aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json14
-rw-r--r--.prettierignore1
-rw-r--r--.prettierrc4
-rw-r--r--.prettierrc.json10
-rw-r--r--.vscode/settings.json7
-rw-r--r--package-lock.json9222
-rw-r--r--package.json65
-rw-r--r--src/.DS_Storebin8196 -> 10244 bytes
-rw-r--r--src/Utils.ts393
-rw-r--r--src/client/DocServer.ts151
-rw-r--r--src/client/Network.ts84
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx281
-rw-r--r--src/client/documents/DocumentTypes.ts99
-rw-r--r--src/client/documents/Documents.ts1697
-rw-r--r--src/client/goldenLayout.js47
-rw-r--r--src/client/util/CaptureManager.tsx157
-rw-r--r--src/client/util/CurrentUserUtils.ts2085
-rw-r--r--src/client/util/DocumentManager.ts251
-rw-r--r--src/client/util/DragManager.ts376
-rw-r--r--src/client/util/DropConverter.ts7
-rw-r--r--src/client/util/GroupManager.tsx260
-rw-r--r--src/client/util/History.ts49
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx2
-rw-r--r--src/client/util/InteractionUtils.tsx2
-rw-r--r--src/client/util/LinkFollower.ts112
-rw-r--r--src/client/util/LinkManager.ts274
-rw-r--r--src/client/util/ReplayMovements.ts208
-rw-r--r--src/client/util/Scripting.ts65
-rw-r--r--src/client/util/ScrollBox.tsx23
-rw-r--r--src/client/util/SelectionManager.ts35
-rw-r--r--src/client/util/SettingsManager.tsx484
-rw-r--r--src/client/util/SharingManager.tsx573
-rw-r--r--src/client/util/SnappingManager.ts45
-rw-r--r--src/client/util/TrackMovements.ts270
-rw-r--r--src/client/util/UndoManager.ts51
-rw-r--r--src/client/views/AudioWaveform.scss2
-rw-r--r--src/client/views/AudioWaveform.tsx153
-rw-r--r--src/client/views/ContextMenu.scss22
-rw-r--r--src/client/views/ContextMenu.tsx129
-rw-r--r--src/client/views/ContextMenuItem.tsx98
-rw-r--r--src/client/views/DashboardView.scss66
-rw-r--r--src/client/views/DashboardView.tsx306
-rw-r--r--src/client/views/DocComponent.tsx155
-rw-r--r--src/client/views/DocumentButtonBar.tsx503
-rw-r--r--src/client/views/DocumentDecorations.scss550
-rw-r--r--src/client/views/DocumentDecorations.tsx852
-rw-r--r--src/client/views/EditableView.tsx113
-rw-r--r--src/client/views/GestureOverlay.scss11
-rw-r--r--src/client/views/GestureOverlay.tsx750
-rw-r--r--src/client/views/GlobalKeyHandler.ts308
-rw-r--r--src/client/views/InkControlPtHandles.tsx1
-rw-r--r--src/client/views/InkStrokeProperties.ts340
-rw-r--r--src/client/views/InkTangentHandles.tsx11
-rw-r--r--src/client/views/InkTranscription.scss5
-rw-r--r--src/client/views/InkTranscription.tsx347
-rw-r--r--src/client/views/InkingStroke.tsx548
-rw-r--r--src/client/views/LightboxView.tsx274
-rw-r--r--src/client/views/Main.scss7
-rw-r--r--src/client/views/Main.tsx66
-rw-r--r--src/client/views/MainView.scss42
-rw-r--r--src/client/views/MainView.tsx1161
-rw-r--r--src/client/views/MarqueeAnnotator.tsx213
-rw-r--r--src/client/views/OverlayView.scss13
-rw-r--r--src/client/views/OverlayView.tsx192
-rw-r--r--src/client/views/Palette.tsx1
-rw-r--r--src/client/views/PreviewCursor.tsx200
-rw-r--r--src/client/views/PropertiesButtons.tsx388
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.scss5
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx53
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx94
-rw-r--r--src/client/views/PropertiesView.tsx1773
-rw-r--r--src/client/views/ScriptBox.tsx134
-rw-r--r--src/client/views/ScriptingRepl.tsx5
-rw-r--r--src/client/views/SidebarAnnos.tsx137
-rw-r--r--src/client/views/StyleProvider.scss2
-rw-r--r--src/client/views/StyleProvider.tsx411
-rw-r--r--src/client/views/TemplateMenu.tsx189
-rw-r--r--src/client/views/Touchable.tsx68
-rw-r--r--src/client/views/_nodeModuleOverrides.scss15
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx286
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx4
-rw-r--r--src/client/views/collections/CollectionDockingView.scss39
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx571
-rw-r--r--src/client/views/collections/CollectionDockingViewMenu.scss34
-rw-r--r--src/client/views/collections/CollectionDockingViewMenu.tsx48
-rw-r--r--src/client/views/collections/CollectionMenu.tsx1365
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss152
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx944
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx376
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewDivider.tsx112
-rw-r--r--src/client/views/collections/CollectionPileView.tsx43
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss206
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx942
-rw-r--r--src/client/views/collections/CollectionStackingView.scss55
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx745
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx335
-rw-r--r--src/client/views/collections/CollectionSubView.tsx303
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx30
-rw-r--r--src/client/views/collections/CollectionTreeView.scss11
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx346
-rw-r--r--src/client/views/collections/CollectionView.tsx239
-rw-r--r--src/client/views/collections/TabDocView.tsx635
-rw-r--r--src/client/views/collections/TreeView.scss40
-rw-r--r--src/client/views/collections/TreeView.tsx1234
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx260
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx243
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx34
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx105
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx1869
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx567
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss4
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx280
-rw-r--r--src/client/views/collections/collectionGrid/Grid.tsx17
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss21
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx289
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx197
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx190
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx7
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx80
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx102
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss27
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx612
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx662
-rw-r--r--src/client/views/global/globalCssVariables.scss2
-rw-r--r--src/client/views/global/globalEnums.tsx1
-rw-r--r--src/client/views/linking/LinkEditor.scss18
-rw-r--r--src/client/views/linking/LinkEditor.tsx348
-rw-r--r--src/client/views/linking/LinkMenu.scss9
-rw-r--r--src/client/views/linking/LinkMenu.tsx85
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx13
-rw-r--r--src/client/views/linking/LinkMenuItem.scss14
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx246
-rw-r--r--src/client/views/linking/LinkPopup.tsx34
-rw-r--r--src/client/views/nodes/AudioBox.scss320
-rw-r--r--src/client/views/nodes/AudioBox.tsx1059
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx17
-rw-r--r--src/client/views/nodes/ColorBox.tsx97
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx10
-rw-r--r--src/client/views/nodes/DataViz.tsx20
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss0
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx90
-rw-r--r--src/client/views/nodes/DataVizBox/DrawHelper.ts247
-rw-r--r--src/client/views/nodes/DataVizBox/HistogramBox.scss18
-rw-r--r--src/client/views/nodes/DataVizBox/HistogramBox.tsx159
-rw-r--r--src/client/views/nodes/DataVizBox/TableBox.scss22
-rw-r--r--src/client/views/nodes/DataVizBox/TableBox.tsx37
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx258
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx5
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx413
-rw-r--r--src/client/views/nodes/DocumentView.scss30
-rw-r--r--src/client/views/nodes/DocumentView.tsx1635
-rw-r--r--src/client/views/nodes/EquationBox.scss5
-rw-r--r--src/client/views/nodes/EquationBox.tsx100
-rw-r--r--src/client/views/nodes/FieldView.tsx54
-rw-r--r--src/client/views/nodes/FilterBox.tsx537
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx76
-rw-r--r--src/client/views/nodes/ImageBox.scss16
-rw-r--r--src/client/views/nodes/ImageBox.tsx406
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx1
-rw-r--r--src/client/views/nodes/LabelBigText.js81
-rw-r--r--src/client/views/nodes/LabelBox.scss2
-rw-r--r--src/client/views/nodes/LabelBox.tsx76
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx168
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx73
-rw-r--r--src/client/views/nodes/LinkDocPreview.scss24
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx221
-rw-r--r--src/client/views/nodes/MapBox/MapBox.scss2
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx535
-rw-r--r--src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx117
-rw-r--r--src/client/views/nodes/PDFBox.tsx529
-rw-r--r--src/client/views/nodes/RecordingBox/ProgressBar.scss123
-rw-r--r--src/client/views/nodes/RecordingBox/ProgressBar.tsx301
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx58
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.scss214
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx271
-rw-r--r--src/client/views/nodes/RecordingBox/index.ts2
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx238
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx790
-rw-r--r--src/client/views/nodes/VideoBox.scss159
-rw-r--r--src/client/views/nodes/VideoBox.tsx1333
-rw-r--r--src/client/views/nodes/WebBox.scss20
-rw-r--r--src/client/views/nodes/WebBox.tsx993
-rw-r--r--src/client/views/nodes/WebBoxRenderer.js30
-rw-r--r--src/client/views/nodes/button/ButtonScripts.ts4
-rw-r--r--src/client/views/nodes/button/FontIconBadge.tsx30
-rw-r--r--src/client/views/nodes/button/FontIconBox.scss95
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx683
-rw-r--r--src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx3
-rw-r--r--src/client/views/nodes/button/textButton/TextButton.tsx23
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx77
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx191
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx282
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx99
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss553
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1305
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx107
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts257
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx380
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts559
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx73
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts380
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts361
-rw-r--r--src/client/views/nodes/formattedText/schema_rts.ts13
-rw-r--r--src/client/views/nodes/trails/PresBox.scss7
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx2490
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss412
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx566
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx258
-rw-r--r--src/client/views/pdf/Annotation.tsx109
-rw-r--r--src/client/views/pdf/PDFViewer.scss50
-rw-r--r--src/client/views/pdf/PDFViewer.tsx574
-rw-r--r--src/client/views/search/SearchBox.tsx219
-rw-r--r--src/client/views/topbar/TopBar.scss278
-rw-r--r--src/client/views/topbar/TopBar.tsx127
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx88
-rw-r--r--src/fields/Doc.ts1014
-rw-r--r--src/fields/InkField.ts6
-rw-r--r--src/fields/List.ts73
-rw-r--r--src/fields/RichTextUtils.ts230
-rw-r--r--src/fields/ScriptField.ts176
-rw-r--r--src/fields/Types.ts9
-rw-r--r--src/fields/URLField.ts67
-rw-r--r--src/fields/documentSchemas.ts7
-rw-r--r--src/fields/util.ts318
-rw-r--r--src/mobile/AudioUpload.tsx5
-rw-r--r--src/mobile/MobileInkOverlay.tsx119
-rw-r--r--src/mobile/MobileInterface.tsx626
-rw-r--r--src/mobile/MobileMain.tsx35
-rw-r--r--src/server/ApiManagers/UploadManager.ts208
-rw-r--r--src/server/ApiManagers/UserManager.ts77
-rw-r--r--src/server/DashSession/Session/agents/applied_session_agent.ts4
-rw-r--r--src/server/DashUploadUtils.ts61
-rw-r--r--src/server/DataVizUtils.ts13
-rw-r--r--src/server/RouteManager.ts58
-rw-r--r--src/server/authentication/AuthenticationManager.ts327
-rw-r--r--src/server/authentication/DashUserModel.ts106
-rw-r--r--src/server/database.ts8
-rw-r--r--src/server/index.ts108
-rw-r--r--src/server/server_Initialization.ts112
-rw-r--r--src/server/websocket.ts194
-rw-r--r--src/typings/index.d.ts85
-rw-r--r--tsconfig.json1
-rw-r--r--tslint.json32
-rw-r--r--views/login.pug4
-rw-r--r--webpack.config.js2
250 files changed, 41615 insertions, 28681 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 000000000..b9f8e1b7a
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,14 @@
+{
+ "extends": ["airbnb", "prettier", "plugin:node/recommended"],
+ "plugins": ["prettier"],
+ "rules": {
+ "prettier/prettier": "error",
+ "no-unused-vars": "warn",
+ "no-console": "off",
+ "func-names": "off",
+ "no-process-exit": "off",
+ "object-shorthand": "off",
+ "class-methods-use-this": "off",
+ "single-quote": "off"
+ }
+}
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..a6372b8eb
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1 @@
+src/client/util/CurrentUserUtils.ts \ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 222861c34..000000000
--- a/.prettierrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "tabWidth": 2,
- "useTabs": false
-}
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 000000000..8f7564e26
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,10 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 4,
+ "semi": true,
+ "singleQuote": true,
+ "singleAttributePerLine": false,
+ "printWidth": 250,
+ "jsxBracketSameLine": true,
+ "arrowParens": "avoid"
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 27acfd1a2..f0cebd6fd 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,12 +4,11 @@
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
- "**/.DS_Store": true,
+ "**/.DS_Store": true
},
"editor.formatOnSave": true,
"editor.detectIndentation": false,
- "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
- "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true,
"search.usePCRE2": true,
"typescript.tsdk": "node_modules/typescript/lib",
-} \ No newline at end of file
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+}
diff --git a/package-lock.json b/package-lock.json
index 2ce90bfe2..3b9cda8bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,220 +5,167 @@
"requires": true,
"dependencies": {
"@babel/code-frame": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
- "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
+ "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
"requires": {
- "@babel/highlight": "^7.8.3"
+ "@babel/highlight": "^7.16.7"
}
},
"@babel/generator": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz",
- "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz",
+ "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==",
"requires": {
- "@babel/types": "^7.9.6",
- "jsesc": "^2.5.1",
- "lodash": "^4.17.13",
- "source-map": "^0.5.0"
- },
- "dependencies": {
- "@babel/types": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz",
- "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==",
- "requires": {
- "@babel/helper-validator-identifier": "^7.9.5",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- }
+ "@babel/types": "^7.18.2",
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "jsesc": "^2.5.1"
}
},
"@babel/helper-annotate-as-pure": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
- "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz",
+ "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==",
"requires": {
- "@babel/types": "^7.10.4"
- },
- "dependencies": {
- "@babel/helper-validator-identifier": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
- "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
- },
- "@babel/types": {
- "version": "7.10.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz",
- "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==",
- "requires": {
- "@babel/helper-validator-identifier": "^7.10.4",
- "lodash": "^4.17.19",
- "to-fast-properties": "^2.0.0"
- }
- },
- "lodash": {
- "version": "4.17.19",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
- "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
- }
+ "@babel/types": "^7.16.7"
}
},
+ "@babel/helper-environment-visitor": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz",
+ "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ=="
+ },
"@babel/helper-function-name": {
- "version": "7.9.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz",
- "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==",
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz",
+ "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==",
"requires": {
- "@babel/helper-get-function-arity": "^7.8.3",
- "@babel/template": "^7.8.3",
- "@babel/types": "^7.9.5"
+ "@babel/template": "^7.16.7",
+ "@babel/types": "^7.17.0"
}
},
- "@babel/helper-get-function-arity": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz",
- "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==",
+ "@babel/helper-hoist-variables": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
+ "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
"requires": {
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.16.7"
}
},
"@babel/helper-module-imports": {
- "version": "7.10.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz",
- "integrity": "sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
+ "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
"requires": {
- "@babel/types": "^7.10.3"
- },
- "dependencies": {
- "@babel/helper-validator-identifier": {
- "version": "7.10.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz",
- "integrity": "sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw=="
- },
- "@babel/types": {
- "version": "7.10.3",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.3.tgz",
- "integrity": "sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA==",
- "requires": {
- "@babel/helper-validator-identifier": "^7.10.3",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- }
+ "@babel/types": "^7.16.7"
}
},
"@babel/helper-plugin-utils": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz",
- "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA=="
+ "version": "7.17.12",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz",
+ "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA=="
},
"@babel/helper-split-export-declaration": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz",
- "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
+ "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
"requires": {
- "@babel/types": "^7.8.3"
+ "@babel/types": "^7.16.7"
}
},
"@babel/helper-validator-identifier": {
- "version": "7.9.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
- "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g=="
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
+ "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
},
"@babel/highlight": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz",
- "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==",
+ "version": "7.17.12",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz",
+ "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==",
"requires": {
- "@babel/helper-validator-identifier": "^7.9.0",
+ "@babel/helper-validator-identifier": "^7.16.7",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz",
- "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q=="
+ "version": "7.18.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz",
+ "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow=="
},
"@babel/plugin-syntax-jsx": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz",
- "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==",
+ "version": "7.17.12",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz",
+ "integrity": "sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog==",
"requires": {
- "@babel/helper-plugin-utils": "^7.16.7"
+ "@babel/helper-plugin-utils": "^7.17.12"
}
},
"@babel/runtime": {
- "version": "7.9.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
- "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz",
+ "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@babel/runtime-corejs3": {
- "version": "7.17.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.7.tgz",
- "integrity": "sha512-TvliGJjhxis5m7xIMvlXH/xG8Oa/LK0SCUCyfKD6nLi42n5fB4WibDJ0g9trmmBB6hwpMNx+Lzbxy9/4gpMaVw==",
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.3.tgz",
+ "integrity": "sha512-l4ddFwrc9rnR+EJsHsh+TJ4A35YqQz/UqcjtlX2ov53hlJYG5CxtQmNZxyajwDVmCxwy++rtvGU5HazCK4W41Q==",
"requires": {
"core-js-pure": "^3.20.2",
"regenerator-runtime": "^0.13.4"
}
},
"@babel/template": {
- "version": "7.8.6",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
- "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==",
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
+ "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
"requires": {
- "@babel/code-frame": "^7.8.3",
- "@babel/parser": "^7.8.6",
- "@babel/types": "^7.8.6"
+ "@babel/code-frame": "^7.16.7",
+ "@babel/parser": "^7.16.7",
+ "@babel/types": "^7.16.7"
}
},
"@babel/traverse": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz",
- "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==",
- "requires": {
- "@babel/code-frame": "^7.8.3",
- "@babel/generator": "^7.9.6",
- "@babel/helper-function-name": "^7.9.5",
- "@babel/helper-split-export-declaration": "^7.8.3",
- "@babel/parser": "^7.9.6",
- "@babel/types": "^7.9.6",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz",
+ "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==",
+ "requires": {
+ "@babel/code-frame": "^7.16.7",
+ "@babel/generator": "^7.18.2",
+ "@babel/helper-environment-visitor": "^7.18.2",
+ "@babel/helper-function-name": "^7.17.9",
+ "@babel/helper-hoist-variables": "^7.16.7",
+ "@babel/helper-split-export-declaration": "^7.16.7",
+ "@babel/parser": "^7.18.0",
+ "@babel/types": "^7.18.2",
"debug": "^4.1.0",
- "globals": "^11.1.0",
- "lodash": "^4.17.13"
+ "globals": "^11.1.0"
},
"dependencies": {
- "@babel/types": {
- "version": "7.9.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz",
- "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==",
- "requires": {
- "@babel/helper-validator-identifier": "^7.9.5",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- },
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"@babel/types": {
- "version": "7.9.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
- "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==",
+ "version": "7.18.4",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz",
+ "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==",
"requires": {
- "@babel/helper-validator-identifier": "^7.9.5",
- "lodash": "^4.17.13",
+ "@babel/helper-validator-identifier": "^7.16.7",
"to-fast-properties": "^2.0.0"
}
},
@@ -228,9 +175,9 @@
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
},
"@emotion/babel-plugin": {
- "version": "11.7.2",
- "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz",
- "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==",
+ "version": "11.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz",
+ "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==",
"requires": {
"@babel/helper-module-imports": "^7.12.13",
"@babel/plugin-syntax-jsx": "^7.12.13",
@@ -246,45 +193,15 @@
"stylis": "4.0.13"
},
"dependencies": {
- "@babel/helper-module-imports": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
- "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
- "requires": {
- "@babel/types": "^7.16.7"
- }
- },
- "@babel/helper-validator-identifier": {
- "version": "7.16.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
- "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
- },
- "@babel/runtime": {
- "version": "7.17.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz",
- "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==",
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- },
- "@babel/types": {
- "version": "7.17.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
- "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
- "requires": {
- "@babel/helper-validator-identifier": "^7.16.7",
- "to-fast-properties": "^2.0.0"
- }
- },
"@emotion/memoize": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz",
+ "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
@@ -299,19 +216,14 @@
"integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
},
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
- },
- "stylis": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
- "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
}
}
},
@@ -352,13 +264,13 @@
}
},
"@emotion/css": {
- "version": "11.7.1",
- "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.7.1.tgz",
- "integrity": "sha512-RUUgPlMZunlc7SE5A6Hg+VWRzb2cU6O9xlV78KCFgcnl25s7Qz/20oQg71iKudpLqk7xj0vhbJlwcJJMT0BOZg==",
+ "version": "11.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.9.0.tgz",
+ "integrity": "sha512-S9UjCxSrxEHawOLnWw4upTwfYKb0gVQdatHejn3W9kPyXxmKv3HmjVfJ84kDLmdX8jR20OuDQwaJ4Um24qD9vA==",
"requires": {
"@emotion/babel-plugin": "^11.7.1",
"@emotion/cache": "^11.7.1",
- "@emotion/serialize": "^1.0.0",
+ "@emotion/serialize": "^1.0.3",
"@emotion/sheet": "^1.0.3",
"@emotion/utils": "^1.0.0"
},
@@ -376,9 +288,9 @@
}
},
"@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz",
+ "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
@@ -398,14 +310,9 @@
"integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
},
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
- },
- "stylis": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
- "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
}
}
},
@@ -415,11 +322,11 @@
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"@emotion/is-prop-valid": {
- "version": "0.8.8",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
- "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz",
+ "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==",
"requires": {
- "@emotion/memoize": "0.7.4"
+ "@emotion/memoize": "^0.7.4"
}
},
"@emotion/memoize": {
@@ -428,27 +335,19 @@
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
},
"@emotion/react": {
- "version": "11.8.2",
- "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.8.2.tgz",
- "integrity": "sha512-+1bcHBaNJv5nkIIgnGKVsie3otS0wF9f1T1hteF3WeVvMNQEtfZ4YyFpnphGoot3ilU/wWMgP2SgIDuHLE/wAA==",
+ "version": "11.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.0.tgz",
+ "integrity": "sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@emotion/babel-plugin": "^11.7.1",
"@emotion/cache": "^11.7.1",
- "@emotion/serialize": "^1.0.2",
+ "@emotion/serialize": "^1.0.3",
"@emotion/utils": "^1.1.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
},
"dependencies": {
- "@babel/runtime": {
- "version": "7.17.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz",
- "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==",
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- },
"@emotion/cache": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
@@ -462,9 +361,9 @@
}
},
"@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz",
+ "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
@@ -484,14 +383,9 @@
"integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
},
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
- },
- "stylis": {
- "version": "4.0.13",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
- "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
}
}
},
@@ -524,26 +418,10 @@
"@emotion/utils": "^1.1.0"
},
"dependencies": {
- "@babel/runtime": {
- "version": "7.17.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz",
- "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==",
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- },
- "@emotion/is-prop-valid": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz",
- "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==",
- "requires": {
- "@emotion/memoize": "^0.7.4"
- }
- },
"@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz",
+ "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
@@ -558,9 +436,9 @@
"integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
},
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
}
}
},
@@ -584,6 +462,102 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
+ "@eslint/eslintrc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
+ "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.3.2",
+ "globals": "^13.15.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "dependencies": {
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "globals": {
+ "version": "13.16.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz",
+ "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "ignore": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ }
+ }
+ },
+ "@ffmpeg/core": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.10.0.tgz",
+ "integrity": "sha512-qunWJl5PezpXEm31tb8Qu5z37B5KVA1VYZCpXchMhuAb3X9T7PuE3SlhOwphEoRhzaOa3lpofDfzihAUMFaVPQ=="
+ },
+ "@ffmpeg/ffmpeg": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.10.0.tgz",
+ "integrity": "sha512-W+d0ysYTO6d4vue/0KMYrxaprh9wvmnPqh6qyHXavBWLrDcE7gI3cJ/EQVfwe9nrt2e0Pi7873P2I18VEDgRfA==",
+ "requires": {
+ "is-url": "^1.2.4",
+ "node-fetch": "^2.6.1",
+ "regenerator-runtime": "^0.13.7",
+ "resolve-url": "^0.2.1"
+ },
+ "dependencies": {
+ "node-fetch": {
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "requires": {
+ "whatwg-url": "^5.0.0"
+ }
+ }
+ }
+ },
"@fortawesome/fontawesome-common-types": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz",
@@ -643,56 +617,28 @@
}
},
"@fortawesome/react-fontawesome": {
- "version": "0.1.18",
- "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz",
- "integrity": "sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==",
+ "version": "0.1.19",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz",
+ "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==",
"requires": {
"prop-types": "^15.8.1"
- },
- "dependencies": {
- "prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "requires": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- }
}
},
"@googlemaps/js-api-loader": {
- "version": "1.12.8",
- "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.12.8.tgz",
- "integrity": "sha512-h1RNVIJkHWL4UrdJo8fwECJwSRg8CSIfTEtPdzvThcji7tAPvVBDVBPzvPKhvl8e8mWEGPhnYOt6kPfSxJL+vQ==",
+ "version": "1.14.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.14.3.tgz",
+ "integrity": "sha512-6iIb+qpGgQpgIHmIFO44WhE1rDUxPVHuezNFL30wRJnkvhwFm94tD291UvNg9L05hLDSoL16jd0lbqqmdy4C5g==",
"requires": {
"fast-deep-equal": "^3.1.3"
- },
- "dependencies": {
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- }
}
},
"@googlemaps/markerclusterer": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-1.0.12.tgz",
- "integrity": "sha512-DSmN2w1ZoI+PBWfNCKYcr+zalP0uaaKIU08+UgebJYZ96X+J6CdUpD+xOVwBV1OJDiIXkneHquOZnT+V6dm+Sg==",
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.0.7.tgz",
+ "integrity": "sha512-/xADL31ERrNoVsF+O2A+H+obxOki4DHPQkU/STS5gFG6ZZVpvNV4WLisW2aOlOsH3rUXSq3MFjqFOEDXSQcvog==",
"requires": {
- "@turf/clusters-dbscan": "^6.4.0",
- "@turf/clusters-kmeans": "^6.4.0",
"fast-deep-equal": "^3.1.3",
"supercluster": "^7.1.3"
- },
- "dependencies": {
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- }
}
},
"@hig/flyout": {
@@ -704,6 +650,19 @@
"emotion": "^10.0.0",
"prop-types": "^15.7.1",
"react-transition-group": "^2.3.1"
+ },
+ "dependencies": {
+ "react-transition-group": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
+ "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
+ "requires": {
+ "dom-helpers": "^3.4.0",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ }
}
},
"@hig/theme-context": {
@@ -716,9 +675,9 @@
}
},
"@hig/theme-data": {
- "version": "2.24.0",
- "resolved": "https://registry.npmjs.org/@hig/theme-data/-/theme-data-2.24.0.tgz",
- "integrity": "sha512-/71T3kezuom7z60Bxyxwxo0oHqb2B59jCbdWrgRgUYorK0vO6zjFmQvRmPSgxq2i1bQxQLx12iuzW9cM494pMQ==",
+ "version": "2.26.0",
+ "resolved": "https://registry.npmjs.org/@hig/theme-data/-/theme-data-2.26.0.tgz",
+ "integrity": "sha512-jEiCieyafBePgFLlIPRPIhNPulWELeOgWJUY4wxdvozRdFUEq50ydx0ysuYPQOh4ucy0jI7Q8FcsBd+aEe7WXQ==",
"requires": {
"tinycolor2": "^1.4.1"
}
@@ -732,6 +691,40 @@
"lodash.memoize": "^4.1.2"
}
},
+ "@humanwhocodes/config-array": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
+ "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==",
+ "dev": true,
+ "requires": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
+ "@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
"@hypnosphi/create-react-context": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz",
@@ -756,20 +749,63 @@
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
},
+ "@jridgewell/gen-mapping": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz",
+ "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==",
+ "requires": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz",
+ "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA=="
+ },
+ "@jridgewell/set-array": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz",
+ "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ=="
+ },
+ "@jridgewell/source-map": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
+ "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz",
+ "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w=="
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz",
+ "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==",
+ "requires": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"@log4js-node/log4js-api": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz",
"integrity": "sha512-6SJfx949YEWooh/CUPpJ+F491y4BYJmknz4hUN1+RHvKoUEynKbRmhnwbk/VLmh4OthLLDNCyWXfbh4DG1cTXA=="
},
"@mapbox/node-pre-gyp": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz",
- "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz",
+ "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==",
"requires": {
- "detect-libc": "^1.0.3",
+ "detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
- "node-fetch": "^2.6.5",
+ "node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
@@ -777,69 +813,6 @@
"tar": "^6.1.11"
},
"dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
- },
- "are-we-there-yet": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
- "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
- "requires": {
- "delegates": "^1.0.0",
- "readable-stream": "^3.6.0"
- }
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
- },
- "gauge": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
- "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
- "requires": {
- "aproba": "^1.0.3 || ^2.0.0",
- "color-support": "^1.1.2",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.1",
- "object-assign": "^4.1.1",
- "signal-exit": "^3.0.0",
- "string-width": "^4.2.3",
- "strip-ansi": "^6.0.1",
- "wide-align": "^1.1.2"
- }
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
- },
- "lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "requires": {
- "yallist": "^4.0.0"
- }
- },
- "make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "requires": {
- "semver": "^6.0.0"
- },
- "dependencies": {
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- }
- }
- },
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -848,79 +821,26 @@
"whatwg-url": "^5.0.0"
}
},
- "npmlog": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
- "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
- "requires": {
- "are-we-there-yet": "^2.0.0",
- "console-control-strings": "^1.1.0",
- "gauge": "^3.0.0",
- "set-blocking": "^2.0.0"
- }
- },
"semver": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
- "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "version": "7.3.7",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+ "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"requires": {
"lru-cache": "^6.0.0"
}
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
- },
- "webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
- },
- "whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
- "requires": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
"@material-ui/core": {
- "version": "4.12.3",
- "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz",
- "integrity": "sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==",
+ "version": "4.12.4",
+ "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz",
+ "integrity": "sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==",
"requires": {
"@babel/runtime": "^7.4.4",
- "@material-ui/styles": "^4.11.4",
- "@material-ui/system": "^4.12.1",
+ "@material-ui/styles": "^4.11.5",
+ "@material-ui/system": "^4.12.2",
"@material-ui/types": "5.1.0",
- "@material-ui/utils": "^4.11.2",
+ "@material-ui/utils": "^4.11.3",
"@types/react-transition-group": "^4.2.0",
"clsx": "^1.0.4",
"hoist-non-react-statics": "^3.3.2",
@@ -931,9 +851,9 @@
},
"dependencies": {
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
},
"dom-helpers": {
"version": "5.2.1",
@@ -958,14 +878,14 @@
}
},
"@material-ui/styles": {
- "version": "4.11.4",
- "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz",
- "integrity": "sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew==",
+ "version": "4.11.5",
+ "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz",
+ "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==",
"requires": {
"@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.8.0",
"@material-ui/types": "5.1.0",
- "@material-ui/utils": "^4.11.2",
+ "@material-ui/utils": "^4.11.3",
"clsx": "^1.0.4",
"csstype": "^2.5.2",
"hoist-non-react-statics": "^3.3.2",
@@ -981,12 +901,12 @@
}
},
"@material-ui/system": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz",
- "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==",
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz",
+ "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==",
"requires": {
"@babel/runtime": "^7.4.4",
- "@material-ui/utils": "^4.11.2",
+ "@material-ui/utils": "^4.11.3",
"csstype": "^2.5.2",
"prop-types": "^15.7.2"
}
@@ -997,9 +917,9 @@
"integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A=="
},
"@material-ui/utils": {
- "version": "4.11.2",
- "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz",
- "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==",
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz",
+ "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==",
"requires": {
"@babel/runtime": "^7.4.4",
"prop-types": "^15.7.2",
@@ -1007,27 +927,27 @@
}
},
"@react-google-maps/api": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.7.0.tgz",
- "integrity": "sha512-Fb/l7aR69ObYvzhpt1d1931454ylRkTPKF1C8GfLvXtg19wc36zTev2Afbahd+qAdyKuQyDdf5/SQyMjYBHI/A==",
- "requires": {
- "@googlemaps/js-api-loader": "1.12.8",
- "@googlemaps/markerclusterer": "1.0.12",
- "@react-google-maps/infobox": "2.6.0",
- "@react-google-maps/marker-clusterer": "2.6.0",
- "@types/google.maps": "3.46.1",
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.12.0.tgz",
+ "integrity": "sha512-BqsclJ9p2I6yZa++0R3oEcneECcH7PFsULhn8UDlWQ9uNAQR/aqvqGb9SXYvdpIAOJdk3E737iTrGhFg4j5Ucw==",
+ "requires": {
+ "@googlemaps/js-api-loader": "1.14.3",
+ "@googlemaps/markerclusterer": "2.0.7",
+ "@react-google-maps/infobox": "2.11.8",
+ "@react-google-maps/marker-clusterer": "2.11.8",
+ "@types/google.maps": "3.49.1",
"invariant": "2.2.4"
}
},
"@react-google-maps/infobox": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.6.0.tgz",
- "integrity": "sha512-2dXVZsuZ2PjelVg+slpUSYPnvoSIySwf4P/wScGijjqu6bpvhyX9AivitsF7zHfbwnZ0gAPX1Q8xtQhKgawkEg=="
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.11.8.tgz",
+ "integrity": "sha512-15y3dniys1Op5HREyLwiJI2pAdeeHRJT5b4WPrS+vyL5KF6iXK1P3y/9bXtnxHiKe2v6iPreaEhR3VgvfRA50Q=="
},
"@react-google-maps/marker-clusterer": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.6.0.tgz",
- "integrity": "sha512-EjeoCM+U/6W9qoGWqurcDbCubqTEDTFET7Jd3XcLHqTLvFAE473x2YbQg98mJXKNgPKcPTNEcTiYYGiIz9tEjA=="
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.11.8.tgz",
+ "integrity": "sha512-OEcdbTXEgPwdpGvVuSrhIhjcZPhlzPz2TvaKCBLraf1PKiDyLOPhlTVRlbIziLiu0xN89pJcill1w9UVIVFF+Q=="
},
"@react-three/fiber": {
"version": "6.2.3",
@@ -1044,16 +964,6 @@
"use-asset": "^1.0.4",
"utility-types": "^3.10.0",
"zustand": "^3.5.1"
- },
- "dependencies": {
- "@babel/runtime": {
- "version": "7.17.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz",
- "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==",
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- }
}
},
"@rkusa/linebreak": {
@@ -1077,68 +987,6 @@
"defer-to-connect": "^2.0.1"
}
},
- "@turf/clone": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz",
- "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==",
- "requires": {
- "@turf/helpers": "^6.5.0"
- }
- },
- "@turf/clusters-dbscan": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-6.5.0.tgz",
- "integrity": "sha512-SxZEE4kADU9DqLRiT53QZBBhu8EP9skviSyl+FGj08Y01xfICM/RR9ACUdM0aEQimhpu+ZpRVcUK+2jtiCGrYQ==",
- "requires": {
- "@turf/clone": "^6.5.0",
- "@turf/distance": "^6.5.0",
- "@turf/helpers": "^6.5.0",
- "@turf/meta": "^6.5.0",
- "density-clustering": "1.3.0"
- }
- },
- "@turf/clusters-kmeans": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-6.5.0.tgz",
- "integrity": "sha512-DwacD5+YO8kwDPKaXwT9DV46tMBVNsbi1IzdajZu1JDSWoN7yc7N9Qt88oi+p30583O0UPVkAK+A10WAQv4mUw==",
- "requires": {
- "@turf/clone": "^6.5.0",
- "@turf/helpers": "^6.5.0",
- "@turf/invariant": "^6.5.0",
- "@turf/meta": "^6.5.0",
- "skmeans": "0.9.7"
- }
- },
- "@turf/distance": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz",
- "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==",
- "requires": {
- "@turf/helpers": "^6.5.0",
- "@turf/invariant": "^6.5.0"
- }
- },
- "@turf/helpers": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz",
- "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw=="
- },
- "@turf/invariant": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz",
- "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==",
- "requires": {
- "@turf/helpers": "^6.5.0"
- }
- },
- "@turf/meta": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz",
- "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==",
- "requires": {
- "@turf/helpers": "^6.5.0"
- }
- },
"@types/adm-zip": {
"version": "0.4.34",
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz",
@@ -1175,14 +1023,14 @@
"dev": true
},
"@types/babel-types": {
- "version": "7.0.7",
- "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz",
- "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ=="
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.11.tgz",
+ "integrity": "sha512-pkPtJUUY+Vwv6B1inAz55rQvivClHJxc9aVEPPmaq2cbyeMLCiDpbKpcKyX4LAwpNGi+SHBv0tHv6+0gXv0P2A=="
},
"@types/babylon": {
- "version": "6.16.5",
- "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz",
- "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==",
+ "version": "6.16.6",
+ "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.6.tgz",
+ "integrity": "sha512-G4yqdVlhr6YhzLXFKy5F7HtRBU8Y23+iWy7UKthMq/OSQnL1hbsoeXESQ2LY8zEDlknipDG3nRGhUC9tkwvy/w==",
"requires": {
"@types/babel-types": "*"
}
@@ -1215,11 +1063,18 @@
}
},
"@types/bson": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.2.tgz",
- "integrity": "sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz",
+ "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==",
"requires": {
"@types/node": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw=="
+ }
}
},
"@types/cacheable-request": {
@@ -1231,6 +1086,13 @@
"@types/keyv": "*",
"@types/node": "*",
"@types/responselike": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw=="
+ }
}
},
"@types/caseless": {
@@ -1240,9 +1102,9 @@
"dev": true
},
"@types/chai": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz",
- "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz",
+ "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==",
"dev": true
},
"@types/color": {
@@ -1266,7 +1128,8 @@
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
- "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
},
"@types/connect": {
"version": "3.4.35",
@@ -1287,9 +1150,9 @@
}
},
"@types/cookie-parser": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz",
- "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==",
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
+ "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
"dev": true,
"requires": {
"@types/express": "*"
@@ -1365,9 +1228,9 @@
}
},
"@types/eslint": {
- "version": "8.4.1",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
- "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==",
+ "version": "8.4.3",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.3.tgz",
+ "integrity": "sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw==",
"dev": true,
"requires": {
"@types/estree": "*",
@@ -1438,9 +1301,9 @@
}
},
"@types/express-session": {
- "version": "1.17.4",
- "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz",
- "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==",
+ "version": "1.17.5",
+ "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.5.tgz",
+ "integrity": "sha512-l0DhkvNVfyUPEEis8fcwbd46VptfA/jmMwHfob2TfDMf3HyPLiB9mKD71LXhz5TMUobODXPD27zXSwtFQLHm+w==",
"dev": true,
"requires": {
"@types/express": "*"
@@ -1468,6 +1331,13 @@
"requires": {
"@types/events": "*",
"@types/node": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw=="
+ }
}
},
"@types/geojson": {
@@ -1503,9 +1373,9 @@
}
},
"@types/google.maps": {
- "version": "3.46.1",
- "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.46.1.tgz",
- "integrity": "sha512-GAa5ZWYgXG50yLXybb7A824esGm/L0LKHS7qD0qkP0IA/Qp5r922P9tmYcbCkGEf3Zgf7Ukbp7l08/IGIJuQwQ=="
+ "version": "3.49.1",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.49.1.tgz",
+ "integrity": "sha512-Sbl5anucT7LUcUxXsRxkCozHdXIkUiY+Tyru+OVl5rot0+VIZuuulmABC7X+nF7rL7BRTAguSBSAD/e/AfIkkA=="
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
@@ -1514,6 +1384,23 @@
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
+ },
+ "dependencies": {
+ "@types/react": {
+ "version": "18.0.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz",
+ "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==",
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "csstype": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
+ }
}
},
"@types/html-minifier-terser": {
@@ -1543,12 +1430,30 @@
"@types/node": "*",
"@types/parse5": "*",
"@types/tough-cookie": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw=="
+ }
}
},
+ "@types/json-buffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz",
+ "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ=="
+ },
"@types/json-schema": {
- "version": "7.0.10",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz",
- "integrity": "sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A=="
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
+ },
+ "@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
},
"@types/keygrip": {
"version": "1.0.2",
@@ -1562,6 +1467,13 @@
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"requires": {
"@types/node": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw=="
+ }
}
},
"@types/libxmljs": {
@@ -1574,9 +1486,9 @@
}
},
"@types/lodash": {
- "version": "4.14.180",
- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.180.tgz",
- "integrity": "sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g==",
+ "version": "4.14.182",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
+ "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"@types/mime": {
@@ -1610,10 +1522,16 @@
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz",
"integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==",
- "dev": true,
"requires": {
"@types/bson": "*",
"@types/node": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw=="
+ }
}
},
"@types/mongoose": {
@@ -1628,7 +1546,8 @@
"@types/node": {
"version": "10.17.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
- "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+ "dev": true
},
"@types/nodemailer": {
"version": "4.6.8",
@@ -1665,9 +1584,9 @@
"integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g=="
},
"@types/passport": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz",
- "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.9.tgz",
+ "integrity": "sha512-9+ilzUhmZQR4JP49GdC2O4UdDE3POPLwpmaTC/iLkW7l0TZCXOo1zsTnnlXPq6rP1UsUZPfbAV4IUdiwiXyC7g==",
"dev": true,
"requires": {
"@types/express": "*"
@@ -1726,9 +1645,9 @@
}
},
"@types/prop-types": {
- "version": "15.7.3",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
- "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"@types/prosemirror-commands": {
"version": "1.0.4",
@@ -1795,9 +1714,9 @@
}
},
"@types/prosemirror-model": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/@types/prosemirror-model/-/prosemirror-model-1.16.1.tgz",
- "integrity": "sha512-SrrCe2cHlYrQ9o55e2i/c3wt1yRajTTpRLvzfmB+2DWjWEbBLTByVWyjrdpKtQTxAaTeU2aeDGo1iuwl/jF27w==",
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/@types/prosemirror-model/-/prosemirror-model-1.16.2.tgz",
+ "integrity": "sha512-1XPJopkKP3oHSBP61uuSuW13DIDZPWvAzP6Pv2/6mixk8EBPUeRGIW548DjJTicMo23gEg1zvCZy9asblQdWag==",
"dev": true,
"requires": {
"@types/orderedmap": "*"
@@ -1815,9 +1734,9 @@
}
},
"@types/prosemirror-state": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@types/prosemirror-state/-/prosemirror-state-1.2.8.tgz",
- "integrity": "sha512-mq9uyQWcpu8jeamO6Callrdvf/e1H/aRLR2kZWSpZrPHctEsxWHBbluD/wqVjXBRIOoMHLf6ZvOkrkmGLoCHVA==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@types/prosemirror-state/-/prosemirror-state-1.3.0.tgz",
+ "integrity": "sha512-nMdUF6w8B++NH4V54X+4GvDty7M02UfuHQW0s1AS25Z4ZrOW4RSY2+s57doXBbeMSjzYV/QoMxCY2sT3KQ2VdQ==",
"dev": true,
"requires": {
"@types/prosemirror-model": "*",
@@ -1826,18 +1745,18 @@
}
},
"@types/prosemirror-transform": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@types/prosemirror-transform/-/prosemirror-transform-1.1.5.tgz",
- "integrity": "sha512-Wr2HXaEF4JPklWpC17RTxE6PxyU54Taqk5FMhK1ojgcN93J+GpkYW8s0mD3rl7KfTmlhVwZPCHE9o0cYf2Go5A==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@types/prosemirror-transform/-/prosemirror-transform-1.4.2.tgz",
+ "integrity": "sha512-FZNzjYm6YUkb1XXOrw2193TiFzwM92ui1nycNaRSd5JDbugf9yBLkXm4Rq3HGJJxBBkRcUE8niqUW5aWlXQQiQ==",
"dev": true,
"requires": {
"@types/prosemirror-model": "*"
}
},
"@types/prosemirror-view": {
- "version": "1.23.1",
- "resolved": "https://registry.npmjs.org/@types/prosemirror-view/-/prosemirror-view-1.23.1.tgz",
- "integrity": "sha512-6e1B2oKUnhmZPUrsVvYjDqeVjE6jGezygjtoHsAK4ZENAxHzHqy5NT4jUvdPTWjCYeH0t2Y7pSfRPNrPIyQX4A==",
+ "version": "1.23.3",
+ "resolved": "https://registry.npmjs.org/@types/prosemirror-view/-/prosemirror-view-1.23.3.tgz",
+ "integrity": "sha512-T5dPDmZiXAazJVSvnx55D6h4mcpiH2q2wTyO9zIeOdox5zx964+zcDl9dFNaXG3qCGlERwMPckhBZL1HCxyygw==",
"dev": true,
"requires": {
"@types/prosemirror-model": "*",
@@ -1867,9 +1786,9 @@
}
},
"@types/react": {
- "version": "16.14.24",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.24.tgz",
- "integrity": "sha512-e7U2WC8XQP/xfR7bwhOhNFZKPTfW1ph+MiqtudKb8tSV8RyCsovQx2sNVtKoOryjxFKpHPPC/yNiGfdeVM5Gyw==",
+ "version": "18.0.15",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz",
+ "integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -1877,9 +1796,9 @@
},
"dependencies": {
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
}
}
},
@@ -1922,23 +1841,32 @@
}
},
"@types/react-dom": {
- "version": "16.9.14",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz",
- "integrity": "sha512-FIX2AVmPTGP30OUJ+0vadeIFJJ07Mh1m+U0rxfgyW34p3rTlXI+nlenvAxNn4BP36YyI9IJ/+UJ7Wu22N1pI7A==",
+ "version": "18.0.6",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz",
+ "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==",
"dev": true,
"requires": {
- "@types/react": "^16"
+ "@types/react": "*"
}
},
"@types/react-grid-layout": {
- "version": "0.17.2",
- "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-0.17.2.tgz",
- "integrity": "sha512-iLKQ5rI9KyPGs/BlWmeBfoikRnoOmTuddK8V5mOCgJ46e2JaecZuPhuIX1kV0mkQSk6X3XoEZALVW0XQbz9g5Q==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz",
+ "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
+ "@types/react-icons": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz",
+ "integrity": "sha512-Vefs6LkLqF61vfV7AiAqls+vpR94q67gunhMueDznG+msAkrYgRxl7gYjNem/kZ+as2l2mNChmF1jRZzzQQtMg==",
+ "dev": true,
+ "requires": {
+ "react-icons": "*"
+ }
+ },
"@types/react-measure": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/@types/react-measure/-/react-measure-2.0.8.tgz",
@@ -1949,22 +1877,59 @@
}
},
"@types/react-reconciler": {
- "version": "0.26.4",
- "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.4.tgz",
- "integrity": "sha512-bdx4aIBkQRDAnzc23JBFeZmVpmfLJHfHikmQukEt9qs4bQtq9f+PDbNwhR9u74FkIUyIDz1I1qJ8OF6RwadKpw==",
+ "version": "0.26.7",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz",
+ "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==",
+ "dev": true,
"requires": {
"@types/react": "*"
+ },
+ "dependencies": {
+ "@types/react": {
+ "version": "18.0.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz",
+ "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "csstype": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==",
+ "dev": true
+ }
}
},
"@types/react-redux": {
- "version": "7.1.23",
- "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.23.tgz",
- "integrity": "sha512-D02o3FPfqQlfu2WeEYwh3x2otYd2Dk1o8wAfsA0B1C2AJEFxE663Ozu7JzuWbznGgW248NaOF6wsqCGNq9d3qw==",
+ "version": "7.1.24",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
+ "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
+ },
+ "dependencies": {
+ "@types/react": {
+ "version": "18.0.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz",
+ "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==",
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "csstype": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
+ }
}
},
"@types/react-select": {
@@ -1988,9 +1953,9 @@
}
},
"@types/react-transition-group": {
- "version": "4.4.4",
- "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
- "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==",
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
"requires": {
"@types/react": "*"
}
@@ -2014,6 +1979,19 @@
"@types/node": "*",
"@types/tough-cookie": "*",
"form-data": "^2.5.0"
+ },
+ "dependencies": {
+ "form-data": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
+ "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
+ "dev": true,
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ }
}
},
"@types/request-promise": {
@@ -2032,6 +2010,13 @@
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"requires": {
"@types/node": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw=="
+ }
}
},
"@types/reveal": {
@@ -2153,9 +2138,9 @@
"integrity": "sha512-6JqTgijtfXcTJik8NtiNxr2L90ex6ElM00qilOGeUcrEsJLOdzLJSIkXHUYS+KPAYQYtRJQKD6XaXds3HjS+gg=="
},
"@types/tough-cookie": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz",
- "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A=="
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
+ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw=="
},
"@types/typescript": {
"version": "2.0.0",
@@ -2167,9 +2152,9 @@
}
},
"@types/uglify-js": {
- "version": "3.13.1",
- "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz",
- "integrity": "sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==",
+ "version": "3.13.3",
+ "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.3.tgz",
+ "integrity": "sha512-9dmBYXt/rKxedUXfCvXSxyiPvpDXLkiRlv17DnqdhS+pRustL1967rI1jZVt1xysTO+xJGMoZzcy3cWC9+b6Tw==",
"dev": true,
"requires": {
"source-map": "^0.6.1"
@@ -2241,14 +2226,6 @@
"@types/connect": "*",
"tapable": "^2.2.0",
"webpack": "^5"
- },
- "dependencies": {
- "tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true
- }
}
},
"@types/webpack-sources": {
@@ -2263,21 +2240,13 @@
},
"dependencies": {
"source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
+ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true
}
}
},
- "@types/webscopeio__react-textarea-autocomplete": {
- "version": "4.7.2",
- "resolved": "https://registry.npmjs.org/@types/webscopeio__react-textarea-autocomplete/-/webscopeio__react-textarea-autocomplete-4.7.2.tgz",
- "integrity": "sha512-e1DZGD+eH19BnllTWCGXAdrMa2kI53wEMuhn/d+wUmnu8//ZI6BiuK/EPdw07fI4+tlyo5qdPZdXdpkoXHJVOw==",
- "requires": {
- "@types/react": "*"
- }
- },
"@types/xregexp": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@types/xregexp/-/xregexp-4.4.0.tgz",
@@ -2288,12 +2257,20 @@
}
},
"@types/yauzl": {
- "version": "2.9.1",
- "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
- "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
"optional": true,
"requires": {
"@types/node": "*"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
+ "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==",
+ "optional": true
+ }
}
},
"@types/youtube": {
@@ -2449,22 +2426,22 @@
}
},
"@webpack-cli/configtest": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz",
- "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg=="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
+ "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg=="
},
"@webpack-cli/info": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz",
- "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==",
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
+ "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
"requires": {
"envinfo": "^7.7.3"
}
},
"@webpack-cli/serve": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz",
- "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw=="
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
+ "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q=="
},
"@webscopeio/react-textarea-autocomplete": {
"version": "4.9.1",
@@ -2478,7 +2455,7 @@
"textarea-caret": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz",
- "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8="
+ "integrity": "sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg=="
}
}
},
@@ -2505,9 +2482,9 @@
"integrity": "sha512-nQvrCBu7K2pSSEtIM0EEF03FVjcczCXInMt3moLNFbjlWx6bZrX72uT6/1uAXDbnzGUAx9gTyDiQ+vrFi663oA=="
},
"abab": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz",
- "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
"dev": true
},
"abbrev": {
@@ -2530,27 +2507,12 @@
"requires": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
- },
- "dependencies": {
- "mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
- },
- "mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "requires": {
- "mime-db": "1.52.0"
- }
- }
}
},
"acorn": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
- "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo="
+ "version": "8.7.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
+ "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A=="
},
"acorn-globals": {
"version": "3.1.0",
@@ -2592,30 +2554,35 @@
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
- "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
+ "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA=="
},
"agent-base": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz",
- "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"requires": {
"debug": "4"
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"ajv": {
- "version": "6.12.2",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
- "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -2637,9 +2604,9 @@
},
"dependencies": {
"ajv": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz",
- "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+ "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -2655,9 +2622,9 @@
}
},
"ajv-keywords": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
- "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ=="
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
},
"align-text": {
"version": "0.1.4",
@@ -2717,6 +2684,23 @@
}
}
},
+ "ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.21.3"
+ },
+ "dependencies": {
+ "type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true
+ }
+ }
+ },
"ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
@@ -2724,9 +2708,9 @@
"dev": true
},
"ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw=="
},
"ansi-styles": {
"version": "3.2.1",
@@ -2761,9 +2745,9 @@
}
},
"aproba": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"archiver": {
"version": "3.1.1",
@@ -2813,28 +2797,12 @@
}
},
"are-we-there-yet": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
- "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
+ "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"requires": {
"delegates": "^1.0.0",
- "readable-stream": "^2.0.6"
- },
- "dependencies": {
- "readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- }
+ "readable-stream": "^3.6.0"
}
},
"arg": {
@@ -2851,6 +2819,16 @@
"sprintf-js": "~1.0.2"
}
},
+ "aria-query": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
+ "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.10.2",
+ "@babel/runtime-corejs3": "^7.10.2"
+ }
+ },
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@@ -2887,9 +2865,9 @@
},
"dependencies": {
"@types/node": {
- "version": "12.12.37",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.37.tgz",
- "integrity": "sha512-4mXKoDptrXAwZErQHrLzpe0FN/0Wmf5JRniSVIdwUrtDf9wnmEV1teCNLBo/TwuXhkK/bVegoEn/wmb+x0AuPg=="
+ "version": "12.20.55",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
+ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
},
"glob": {
"version": "7.1.3",
@@ -2904,6 +2882,14 @@
"path-is-absolute": "^1.0.0"
}
},
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
"mocha": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz",
@@ -2960,6 +2946,19 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
+ "array-includes": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz",
+ "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.19.5",
+ "get-intrinsic": "^1.1.1",
+ "is-string": "^1.0.7"
+ }
+ },
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -2980,6 +2979,42 @@
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
},
+ "array.prototype.flat": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
+ "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.2",
+ "es-shim-unscopables": "^1.0.0"
+ }
+ },
+ "array.prototype.flatmap": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz",
+ "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.2",
+ "es-shim-unscopables": "^1.0.0"
+ }
+ },
+ "array.prototype.reduce": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz",
+ "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.2",
+ "es-array-method-boxes-properly": "^1.0.0",
+ "is-string": "^1.0.7"
+ }
+ },
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
@@ -2996,9 +3031,9 @@
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"asn1": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
- "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"requires": {
"safer-buffer": "~2.1.0"
}
@@ -3018,10 +3053,22 @@
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
},
+ "ast-types-flow": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
+ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
+ "dev": true
+ },
+ "astral-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+ "dev": true
+ },
"async": {
- "version": "2.6.3",
- "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
- "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+ "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"requires": {
"lodash": "^4.17.14"
}
@@ -3071,6 +3118,25 @@
"mkdirp": "^0.5.1",
"source-map-support": "^0.5.3",
"webpack-log": "^1.2.0"
+ },
+ "dependencies": {
+ "enhanced-resolve": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz",
+ "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.5.0",
+ "tapable": "^1.0.0"
+ }
+ },
+ "tapable": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+ "dev": true
+ }
}
},
"aws-sign2": {
@@ -3079,9 +3145,15 @@
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
- "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+ },
+ "axe-core": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.2.tgz",
+ "integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==",
+ "dev": true
},
"axios": {
"version": "0.19.2",
@@ -3089,31 +3161,14 @@
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
"requires": {
"follow-redirects": "1.5.10"
- },
- "dependencies": {
- "debug": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "follow-redirects": {
- "version": "1.5.10",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
- "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
- "requires": {
- "debug": "=3.1.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- }
}
},
+ "axobject-query": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
+ "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==",
+ "dev": true
+ },
"babel": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel/-/babel-6.23.0.tgz",
@@ -3178,6 +3233,28 @@
}
}
},
+ "babel-eslint": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
+ "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.7.0",
+ "@babel/traverse": "^7.7.0",
+ "@babel/types": "^7.7.0",
+ "eslint-visitor-keys": "^1.0.0",
+ "resolve": "^1.12.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ }
+ }
+ },
"babel-plugin-emotion": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz",
@@ -3206,14 +3283,15 @@
}
},
"babel-plugin-styled-components": {
- "version": "1.10.7",
- "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.7.tgz",
- "integrity": "sha512-MBMHGcIA22996n9hZRf/UJLVVgkEOITuR2SvjHLb5dSTUyR4ZRGn+ngITapes36FI3WLxZHfRhkA1ffHxihOrg==",
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz",
+ "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==",
"requires": {
- "@babel/helper-annotate-as-pure": "^7.0.0",
- "@babel/helper-module-imports": "^7.0.0",
+ "@babel/helper-annotate-as-pure": "^7.16.0",
+ "@babel/helper-module-imports": "^7.16.0",
"babel-plugin-syntax-jsx": "^6.18.0",
- "lodash": "^4.17.11"
+ "lodash": "^4.17.11",
+ "picomatch": "^2.3.0"
}
},
"babel-plugin-syntax-jsx": {
@@ -3231,9 +3309,9 @@
},
"dependencies": {
"core-js": {
- "version": "2.6.11",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
- "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+ "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
},
"regenerator-runtime": {
"version": "0.11.1",
@@ -3268,12 +3346,12 @@
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
- "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
+ "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA=="
},
"balanced-match": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base": {
"version": "0.11.2",
@@ -3333,12 +3411,12 @@
"base64-arraybuffer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
- "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
+ "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg=="
},
"base64-js": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
- "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"base64id": {
"version": "2.0.0",
@@ -3385,19 +3463,28 @@
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
},
"bignumber.js": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
- "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ=="
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz",
+ "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw=="
},
"binary-extensions": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
},
+ "bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "optional": true,
+ "requires": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
"bl": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
- "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
@@ -3422,26 +3509,23 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
- "blueimp-load-image": {
- "version": "2.31.0",
- "resolved": "https://registry.npmjs.org/blueimp-load-image/-/blueimp-load-image-2.31.0.tgz",
- "integrity": "sha512-6sTGh1OiUmuH8ftAYvUzALivoOmcnahinGmjZFI4puZVowXoKTn/bXtth7N1skW5AlezEOfjgFH4lNXHeNRQog=="
- },
"body-parser": {
- "version": "1.19.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
- "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+ "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
"requires": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
- "depd": "~1.1.2",
- "http-errors": "1.8.1",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
"iconv-lite": "0.4.24",
- "on-finished": "~2.3.0",
- "qs": "6.9.7",
- "raw-body": "2.4.3",
- "type-is": "~1.6.18"
+ "on-finished": "2.4.1",
+ "qs": "6.10.3",
+ "raw-body": "2.5.1",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
},
"dependencies": {
"debug": {
@@ -3452,15 +3536,26 @@
"ms": "2.0.0"
}
},
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"qs": {
- "version": "6.9.7",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
- "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw=="
+ "version": "6.10.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+ "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
}
}
},
@@ -3555,11 +3650,6 @@
"requires": {
"is-extendable": "^0.1.0"
}
- },
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
}
}
},
@@ -3580,32 +3670,32 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
},
"browserslist": {
- "version": "4.20.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
- "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
+ "version": "4.20.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz",
+ "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==",
"requires": {
- "caniuse-lite": "^1.0.30001317",
- "electron-to-chromium": "^1.4.84",
+ "caniuse-lite": "^1.0.30001349",
+ "electron-to-chromium": "^1.4.147",
"escalade": "^3.1.1",
- "node-releases": "^2.0.2",
+ "node-releases": "^2.0.5",
"picocolors": "^1.0.0"
}
},
"bson": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz",
- "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==",
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz",
+ "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==",
"requires": {
"buffer": "^5.6.0"
}
},
"buffer": {
- "version": "5.6.0",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
- "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
- "base64-js": "^1.0.2",
- "ieee754": "^1.1.4"
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
}
},
"buffer-crc32": {
@@ -3619,9 +3709,9 @@
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"buffer-from": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
- "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"buffer-indexof": {
"version": "1.1.1",
@@ -3643,10 +3733,9 @@
}
},
"builtin-modules": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
- "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
- "dev": true
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+ "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw=="
},
"bytes": {
"version": "3.1.2",
@@ -3674,6 +3763,12 @@
"y18n": "^4.0.0"
},
"dependencies": {
+ "chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true
+ },
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
@@ -3795,13 +3890,6 @@
"requires": {
"pascal-case": "^3.1.2",
"tslib": "^2.0.3"
- },
- "dependencies": {
- "tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
- }
}
},
"camelcase": {
@@ -3831,25 +3919,18 @@
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
},
"caniuse-lite": {
- "version": "1.0.30001317",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz",
- "integrity": "sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ=="
+ "version": "1.0.30001351",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001351.tgz",
+ "integrity": "sha512-u+Ll+RDaiQEproTQjZLjZwyfNgNezA1fERMT7/54npcz+PkbVJUAHXMUz4bkXQYRPWrcFNO0Fbi1mwjfXg6N5g=="
},
"canvas": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.0.tgz",
- "integrity": "sha512-0l93g7uxp7rMyr7H+XRQ28A3ud0dKIUTIEkUe1Dxh4rjUYN7B93+SjC3r1PDKA18xcQN87OFGgUnyw7LSgNLSQ==",
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.3.tgz",
+ "integrity": "sha512-WOUM7ghii5TV2rbhaZkh1youv/vW1/Canev6Yx6BG2W+1S07w8jKZqKkPnbiPpQEDsnJdN8ouDd7OvQEGXDcUw==",
"requires": {
"@mapbox/node-pre-gyp": "^1.0.0",
"nan": "^2.15.0",
"simple-get": "^3.0.3"
- },
- "dependencies": {
- "nan": {
- "version": "2.15.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
- "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
- }
}
},
"capture-stack-trace": {
@@ -3913,6 +3994,12 @@
"is-regex": "^1.0.3"
}
},
+ "chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "dev": true
+ },
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -3940,573 +4027,12 @@
"path-is-absolute": "^1.0.0",
"readdirp": "^2.2.1",
"upath": "^1.1.1"
- },
- "dependencies": {
- "bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "optional": true,
- "requires": {
- "file-uri-to-path": "1.0.0"
- }
- },
- "fsevents": {
- "version": "1.2.12",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz",
- "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==",
- "optional": true,
- "requires": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1",
- "node-pre-gyp": "*"
- },
- "dependencies": {
- "abbrev": {
- "version": "1.1.1",
- "resolved": false,
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "optional": true
- },
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": false,
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "optional": true
- },
- "aproba": {
- "version": "1.2.0",
- "resolved": false,
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
- "optional": true
- },
- "are-we-there-yet": {
- "version": "1.1.5",
- "resolved": false,
- "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
- "optional": true,
- "requires": {
- "delegates": "^1.0.0",
- "readable-stream": "^2.0.6"
- }
- },
- "balanced-match": {
- "version": "1.0.0",
- "resolved": false,
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
- "optional": true
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": false,
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "optional": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "chownr": {
- "version": "1.1.4",
- "resolved": false,
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "optional": true
- },
- "code-point-at": {
- "version": "1.1.0",
- "resolved": false,
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
- "optional": true
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": false,
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "optional": true
- },
- "console-control-strings": {
- "version": "1.1.0",
- "resolved": false,
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "optional": true
- },
- "core-util-is": {
- "version": "1.0.2",
- "resolved": false,
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "optional": true
- },
- "debug": {
- "version": "3.2.6",
- "resolved": false,
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
- "optional": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "deep-extend": {
- "version": "0.6.0",
- "resolved": false,
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
- "optional": true
- },
- "delegates": {
- "version": "1.0.0",
- "resolved": false,
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
- "optional": true
- },
- "detect-libc": {
- "version": "1.0.3",
- "resolved": false,
- "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
- "optional": true
- },
- "fs-minipass": {
- "version": "1.2.7",
- "resolved": false,
- "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
- "optional": true,
- "requires": {
- "minipass": "^2.6.0"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": false,
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "optional": true
- },
- "gauge": {
- "version": "2.7.4",
- "resolved": false,
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
- "optional": true,
- "requires": {
- "aproba": "^1.0.3",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.0",
- "object-assign": "^4.1.0",
- "signal-exit": "^3.0.0",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wide-align": "^1.1.0"
- }
- },
- "glob": {
- "version": "7.1.6",
- "resolved": false,
- "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
- "optional": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "has-unicode": {
- "version": "2.0.1",
- "resolved": false,
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
- "optional": true
- },
- "iconv-lite": {
- "version": "0.4.24",
- "resolved": false,
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "optional": true,
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3"
- }
- },
- "ignore-walk": {
- "version": "3.0.3",
- "resolved": false,
- "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
- "optional": true,
- "requires": {
- "minimatch": "^3.0.4"
- }
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": false,
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "optional": true,
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": false,
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "optional": true
- },
- "ini": {
- "version": "1.3.5",
- "resolved": false,
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
- "optional": true
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": false,
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "optional": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": false,
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "optional": true
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": false,
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "optional": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minimist": {
- "version": "1.2.5",
- "resolved": false,
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
- "optional": true
- },
- "minipass": {
- "version": "2.9.0",
- "resolved": false,
- "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
- "optional": true,
- "requires": {
- "safe-buffer": "^5.1.2",
- "yallist": "^3.0.0"
- }
- },
- "minizlib": {
- "version": "1.3.3",
- "resolved": false,
- "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
- "optional": true,
- "requires": {
- "minipass": "^2.9.0"
- }
- },
- "mkdirp": {
- "version": "0.5.3",
- "resolved": false,
- "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==",
- "optional": true,
- "requires": {
- "minimist": "^1.2.5"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": false,
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "optional": true
- },
- "needle": {
- "version": "2.3.3",
- "resolved": false,
- "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==",
- "optional": true,
- "requires": {
- "debug": "^3.2.6",
- "iconv-lite": "^0.4.4",
- "sax": "^1.2.4"
- }
- },
- "node-pre-gyp": {
- "version": "0.14.0",
- "resolved": false,
- "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
- "optional": true,
- "requires": {
- "detect-libc": "^1.0.2",
- "mkdirp": "^0.5.1",
- "needle": "^2.2.1",
- "nopt": "^4.0.1",
- "npm-packlist": "^1.1.6",
- "npmlog": "^4.0.2",
- "rc": "^1.2.7",
- "rimraf": "^2.6.1",
- "semver": "^5.3.0",
- "tar": "^4.4.2"
- }
- },
- "nopt": {
- "version": "4.0.3",
- "resolved": false,
- "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
- "optional": true,
- "requires": {
- "abbrev": "1",
- "osenv": "^0.1.4"
- }
- },
- "npm-bundled": {
- "version": "1.1.1",
- "resolved": false,
- "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
- "optional": true,
- "requires": {
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "npm-normalize-package-bin": {
- "version": "1.0.1",
- "resolved": false,
- "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
- "optional": true
- },
- "npm-packlist": {
- "version": "1.4.8",
- "resolved": false,
- "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
- "optional": true,
- "requires": {
- "ignore-walk": "^3.0.1",
- "npm-bundled": "^1.0.1",
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "npmlog": {
- "version": "4.1.2",
- "resolved": false,
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
- "optional": true,
- "requires": {
- "are-we-there-yet": "~1.1.2",
- "console-control-strings": "~1.1.0",
- "gauge": "~2.7.3",
- "set-blocking": "~2.0.0"
- }
- },
- "number-is-nan": {
- "version": "1.0.1",
- "resolved": false,
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "optional": true
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": false,
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
- "optional": true
- },
- "once": {
- "version": "1.4.0",
- "resolved": false,
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "optional": true,
- "requires": {
- "wrappy": "1"
- }
- },
- "os-homedir": {
- "version": "1.0.2",
- "resolved": false,
- "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
- "optional": true
- },
- "os-tmpdir": {
- "version": "1.0.2",
- "resolved": false,
- "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
- "optional": true
- },
- "osenv": {
- "version": "0.1.5",
- "resolved": false,
- "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
- "optional": true,
- "requires": {
- "os-homedir": "^1.0.0",
- "os-tmpdir": "^1.0.0"
- }
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": false,
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "optional": true
- },
- "process-nextick-args": {
- "version": "2.0.1",
- "resolved": false,
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "optional": true
- },
- "rc": {
- "version": "1.2.8",
- "resolved": false,
- "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
- "optional": true,
- "requires": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- }
- },
- "readable-stream": {
- "version": "2.3.7",
- "resolved": false,
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "optional": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "rimraf": {
- "version": "2.7.1",
- "resolved": false,
- "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
- "optional": true,
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": false,
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "optional": true
- },
- "safer-buffer": {
- "version": "2.1.2",
- "resolved": false,
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "optional": true
- },
- "sax": {
- "version": "1.2.4",
- "resolved": false,
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
- "optional": true
- },
- "semver": {
- "version": "5.7.1",
- "resolved": false,
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "optional": true
- },
- "set-blocking": {
- "version": "2.0.0",
- "resolved": false,
- "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
- "optional": true
- },
- "signal-exit": {
- "version": "3.0.2",
- "resolved": false,
- "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
- "optional": true
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": false,
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "optional": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "string_decoder": {
- "version": "1.1.1",
- "resolved": false,
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "optional": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": false,
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "optional": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "strip-json-comments": {
- "version": "2.0.1",
- "resolved": false,
- "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
- "optional": true
- },
- "tar": {
- "version": "4.4.13",
- "resolved": false,
- "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
- "optional": true,
- "requires": {
- "chownr": "^1.1.1",
- "fs-minipass": "^1.2.5",
- "minipass": "^2.8.6",
- "minizlib": "^1.2.1",
- "mkdirp": "^0.5.0",
- "safe-buffer": "^5.1.2",
- "yallist": "^3.0.3"
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": false,
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "optional": true
- },
- "wide-align": {
- "version": "1.1.3",
- "resolved": false,
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
- "optional": true,
- "requires": {
- "string-width": "^1.0.2 || 2"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": false,
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "optional": true
- },
- "yallist": {
- "version": "3.1.1",
- "resolved": false,
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "optional": true
- }
- }
- }
}
},
"chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
},
"chrome": {
"version": "0.1.0",
@@ -4560,14 +4086,14 @@
}
},
"classnames": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
- "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"clean-css": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
- "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
+ "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==",
"requires": {
"source-map": "~0.6.0"
},
@@ -4584,6 +4110,31 @@
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
},
+ "cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "requires": {
+ "restore-cursor": "^3.1.0"
+ }
+ },
+ "cli-width": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
+ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+ "dev": true
+ },
+ "clipboard": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
+ "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
+ "requires": {
+ "good-listener": "^1.2.2",
+ "select": "^1.1.2",
+ "tiny-emitter": "^2.0.0"
+ }
+ },
"cliss": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/cliss/-/cliss-0.0.2.tgz",
@@ -4625,9 +4176,9 @@
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
},
"string-width": {
"version": "3.1.0",
@@ -4721,9 +4272,9 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz",
- "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==",
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
@@ -4735,9 +4286,9 @@
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
},
"colorette": {
- "version": "2.0.16",
- "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
- "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g=="
+ "version": "2.0.17",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.17.tgz",
+ "integrity": "sha512-hJo+3Bkn0NCHybn9Tu35fIeoOKGOk5OCC32y4Hz2It+qlCO2Q3DeQ1hRn/tDDMQKRYUEzqsl7jbF6dYKjlE60g=="
},
"colors": {
"version": "1.4.0",
@@ -4769,9 +4320,9 @@
}
},
"commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
},
"commondir": {
"version": "1.0.1",
@@ -4782,7 +4333,7 @@
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
- "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
+ "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
},
"component-emitter": {
"version": "1.3.0",
@@ -4792,7 +4343,16 @@
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
- "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
+ "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
+ },
+ "compress-brotli": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.8.tgz",
+ "integrity": "sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==",
+ "requires": {
+ "@types/json-buffer": "~3.0.0",
+ "json-buffer": "~3.0.1"
+ }
},
"compress-commons": {
"version": "2.1.1",
@@ -4901,18 +4461,39 @@
}
},
"configstore": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
- "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz",
+ "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==",
"requires": {
- "dot-prop": "^4.1.0",
+ "dot-prop": "^4.2.1",
"graceful-fs": "^4.1.2",
"make-dir": "^1.0.0",
"unique-string": "^1.0.0",
"write-file-atomic": "^2.0.0",
"xdg-basedir": "^3.0.0"
+ },
+ "dependencies": {
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="
+ }
}
},
+ "confusing-browser-globals": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
+ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==",
+ "dev": true
+ },
"connect-flash": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz",
@@ -5067,13 +4648,6 @@
"requires": {
"depd": "~2.0.0",
"keygrip": "~1.1.0"
- },
- "dependencies": {
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
- }
}
},
"copy-concurrently": {
@@ -5090,6 +4664,12 @@
"run-queue": "^1.0.0"
},
"dependencies": {
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -5136,6 +4716,12 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
"dev": true
+ },
+ "serialize-javascript": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
+ "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
+ "dev": true
}
}
},
@@ -5145,14 +4731,14 @@
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
},
"core-js-pure": {
- "version": "3.21.1",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz",
- "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ=="
+ "version": "3.22.8",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.8.tgz",
+ "integrity": "sha512-bOxbZIy9S5n4OVH63XaLVXZ49QKicjowDx/UELyJ68vxfCRpYsbyh/WNZNfEfAk+ekA8vSjt+gCDpvh672bc3w=="
},
"core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"cors": {
"version": "2.8.5",
@@ -5265,25 +4851,6 @@
"requires": {
"whatwg-url": "^5.0.0"
}
- },
- "tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
- },
- "webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
- },
- "whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
- "requires": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
}
}
},
@@ -5312,6 +4879,11 @@
}
}
},
+ "crypto-js": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz",
+ "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q=="
+ },
"crypto-random-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
@@ -5356,37 +4928,24 @@
"postcss-modules-values": "^2.0.0",
"postcss-value-parser": "^3.3.0",
"schema-utils": "^1.0.0"
- },
- "dependencies": {
- "schema-utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
- "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
- "dev": true,
- "requires": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- }
- }
}
},
"css-select": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz",
- "integrity": "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
+ "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
"requires": {
"boolbase": "^1.0.0",
- "css-what": "^5.1.0",
- "domhandler": "^4.3.0",
+ "css-what": "^6.0.1",
+ "domhandler": "^4.3.1",
"domutils": "^2.8.0",
"nth-check": "^2.0.1"
},
"dependencies": {
"dom-serializer": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
- "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
@@ -5394,14 +4953,14 @@
}
},
"domelementtype": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
- "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
},
"domhandler": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz",
- "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
"requires": {
"domelementtype": "^2.2.0"
}
@@ -5443,9 +5002,9 @@
}
},
"css-what": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
- "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw=="
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="
},
"cssesc": {
"version": "3.0.0",
@@ -5460,9 +5019,9 @@
"dev": true
},
"cssstyle": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.2.0.tgz",
- "integrity": "sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
"dev": true,
"requires": {
"cssom": "~0.3.6"
@@ -5477,9 +5036,9 @@
}
},
"csstype": {
- "version": "2.6.10",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
- "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
+ "version": "2.6.20",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
+ "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
},
"currently-unhandled": {
"version": "0.4.1",
@@ -5492,7 +5051,7 @@
"custom-event": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
- "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU="
+ "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
},
"cyclist": {
"version": "1.0.1",
@@ -5500,21 +5059,14 @@
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
"dev": true
},
- "d": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
- "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
- "dev": true,
+ "d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"requires": {
- "es5-ext": "^0.10.50",
- "type": "^1.0.1"
+ "internmap": "^1.0.0"
}
},
- "d3-array": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
- "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
- },
"d3-axis": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.1.0.tgz",
@@ -5572,16 +5124,6 @@
"d3-interpolate": "1.2.0 - 2",
"d3-time": "^2.1.1",
"d3-time-format": "2 - 3"
- },
- "dependencies": {
- "d3-array": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
- "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
- "requires": {
- "internmap": "^1.0.0"
- }
- }
}
},
"d3-selection": {
@@ -5603,16 +5145,6 @@
"integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
"requires": {
"d3-array": "2"
- },
- "dependencies": {
- "d3-array": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
- "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
- "requires": {
- "internmap": "^1.0.0"
- }
- }
}
},
"d3-time-format": {
@@ -5652,6 +5184,12 @@
"d3-transition": "2"
}
},
+ "damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -5669,6 +5207,34 @@
"abab": "^2.0.0",
"whatwg-mimetype": "^2.2.0",
"whatwg-url": "^7.0.0"
+ },
+ "dependencies": {
+ "tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true
+ },
+ "whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "dev": true,
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ }
}
},
"date-fns": {
@@ -5739,9 +5305,9 @@
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
"deep-is": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
"deepmerge": {
@@ -5804,11 +5370,12 @@
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="
},
"define-properties": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
- "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+ "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
"requires": {
- "object-keys": "^1.0.12"
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
}
},
"define-property": {
@@ -5884,6 +5451,12 @@
}
}
},
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ },
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -5900,6 +5473,11 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
+ "delegate": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+ "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+ },
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -5910,11 +5488,6 @@
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw=="
},
- "density-clustering": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/density-clustering/-/density-clustering-1.3.0.tgz",
- "integrity": "sha1-3J9ZyPCrl+FiSsZJMP0xlIF9ysU="
- },
"depcheck": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/depcheck/-/depcheck-0.9.2.tgz",
@@ -5940,24 +5513,18 @@
},
"dependencies": {
"ansi-regex": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
- "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
- "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
- "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
- "builtin-modules": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
- "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw=="
- },
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -5993,11 +5560,11 @@
}
},
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"emoji-regex": {
@@ -6036,6 +5603,11 @@
"p-locate": "^4.1.0"
}
},
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
@@ -6064,21 +5636,21 @@
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
},
"string-width": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
- "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.0"
+ "strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
- "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"requires": {
- "ansi-regex": "^5.0.0"
+ "ansi-regex": "^5.0.1"
}
},
"wrap-ansi": {
@@ -6092,9 +5664,9 @@
}
},
"yargs": {
- "version": "15.3.1",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
- "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
@@ -6106,7 +5678,7 @@
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
- "yargs-parser": "^18.1.1"
+ "yargs-parser": "^18.1.2"
}
},
"yargs-parser": {
@@ -6121,9 +5693,9 @@
}
},
"depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
"deps-regex": {
"version": "0.1.4",
@@ -6131,14 +5703,14 @@
"integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ="
},
"destroy": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
- "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
},
"detect-libc": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
+ "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w=="
},
"detect-node": {
"version": "2.1.0",
@@ -6207,6 +5779,15 @@
"buffer-indexof": "^1.0.0"
}
},
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
"doctypes": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
@@ -6238,14 +5819,14 @@
},
"dependencies": {
"domelementtype": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
- "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
},
"entities": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
- "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
}
}
},
@@ -6261,6 +5842,14 @@
"dev": true,
"requires": {
"webidl-conversions": "^4.0.2"
+ },
+ "dependencies": {
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true
+ }
}
},
"domhandler": {
@@ -6271,6 +5860,11 @@
"domelementtype": "1"
}
},
+ "dommatrix": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz",
+ "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww=="
+ },
"domutils": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
@@ -6287,19 +5881,12 @@
"requires": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
- },
- "dependencies": {
- "tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
- }
}
},
"dot-prop": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
- "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
+ "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
"requires": {
"is-obj": "^1.0.0"
}
@@ -6381,9 +5968,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
- "version": "1.4.86",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.86.tgz",
- "integrity": "sha512-EVTZ+igi8x63pK4bPuA95PXIs2b2Cowi3WQwI9f9qManLiZJOD1Lash1J3W4TvvcUCcIR4o/rgi9o8UicXSO+w=="
+ "version": "1.4.149",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.149.tgz",
+ "integrity": "sha512-SR6ZQ8gfXX7LIS3UTDXu1CbFMUnluP3TS+jP7QwDwOnoH5CcHbejFdaaaEnGxR6I76E55eFnUV9mxI8GKufkEg=="
},
"emoji-regex": {
"version": "7.0.3",
@@ -6410,11 +5997,11 @@
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"encoding": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
- "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"requires": {
- "iconv-lite": "~0.4.13"
+ "iconv-lite": "^0.6.2"
}
},
"end-of-stream": {
@@ -6426,9 +6013,9 @@
}
},
"engine.io": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
- "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz",
+ "integrity": "sha512-Kc8fo5bbg8F4a2f3HPHTEpGyq/IRIQpyeHu3H1ThR14XDD7VrLcsGBo16HUpahgp8YkHJDaU5gNxJZbuGcuueg==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
@@ -6482,7 +6069,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"ws": {
"version": "7.4.6",
@@ -6504,14 +6091,13 @@
}
},
"enhanced-resolve": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
- "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==",
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
+ "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==",
"dev": true,
"requires": {
- "graceful-fs": "^4.1.2",
- "memory-fs": "^0.5.0",
- "tapable": "^1.0.0"
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
}
},
"entities": {
@@ -6533,9 +6119,9 @@
}
},
"errno": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
- "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
+ "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"dev": true,
"requires": {
"prr": "~1.0.1"
@@ -6550,29 +6136,68 @@
}
},
"es-abstract": {
- "version": "1.17.5",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
- "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
+ "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
"requires": {
+ "call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.1.1",
+ "get-symbol-description": "^1.0.0",
"has": "^1.0.3",
- "has-symbols": "^1.0.1",
- "is-callable": "^1.1.5",
- "is-regex": "^1.0.5",
- "object-inspect": "^1.7.0",
+ "has-property-descriptors": "^1.0.0",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "is-callable": "^1.2.4",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.0",
"object-keys": "^1.1.1",
- "object.assign": "^4.1.0",
- "string.prototype.trimleft": "^2.1.1",
- "string.prototype.trimright": "^2.1.1"
+ "object.assign": "^4.1.2",
+ "regexp.prototype.flags": "^1.4.3",
+ "string.prototype.trimend": "^1.0.5",
+ "string.prototype.trimstart": "^1.0.5",
+ "unbox-primitive": "^1.0.2"
+ },
+ "dependencies": {
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ }
}
},
+ "es-array-method-boxes-properly": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
+ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
+ },
"es-module-lexer": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
"integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==",
"dev": true
},
+ "es-shim-unscopables": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+ "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
@@ -6583,28 +6208,6 @@
"is-symbol": "^1.0.2"
}
},
- "es5-ext": {
- "version": "0.10.53",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
- "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
- "dev": true,
- "requires": {
- "es6-iterator": "~2.0.3",
- "es6-symbol": "~3.1.3",
- "next-tick": "~1.0.0"
- }
- },
- "es6-iterator": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
- "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
- "dev": true,
- "requires": {
- "d": "1",
- "es5-ext": "^0.10.35",
- "es6-symbol": "^3.1.1"
- }
- },
"es6-promise": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -6616,7 +6219,6 @@
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"dev": true,
"requires": {
- "d": "^1.0.1",
"ext": "^1.1.2"
}
},
@@ -6636,9 +6238,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"escodegen": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz",
- "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==",
+ "version": "1.14.3",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
+ "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
"dev": true,
"requires": {
"esprima": "^4.0.1",
@@ -6657,6 +6259,1111 @@
}
}
},
+ "eslint": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz",
+ "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==",
+ "dev": true,
+ "requires": {
+ "@eslint/eslintrc": "^1.3.0",
+ "@humanwhocodes/config-array": "^0.9.2",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.2",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^6.0.1",
+ "globals": "^13.15.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ },
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ },
+ "globals": {
+ "version": "13.16.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz",
+ "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "ignore": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "eslint-config-airbnb": {
+ "version": "19.0.4",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz",
+ "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==",
+ "dev": true,
+ "requires": {
+ "eslint-config-airbnb-base": "^15.0.0",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5"
+ },
+ "dependencies": {
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ }
+ }
+ },
+ "eslint-config-airbnb-base": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz",
+ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-config-esnext": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-esnext/-/eslint-config-esnext-4.1.0.tgz",
+ "integrity": "sha512-GhfVEXdqYKEIIj7j+Fw2SQdL9qyZMekgXfq6PyXM66cQw0B435ddjz3P3kxOBVihMRJ0xGYjosaveQz5Y6z0uA==",
+ "dev": true,
+ "requires": {
+ "babel-eslint": "^10.0.1",
+ "eslint": "^6.8.0",
+ "eslint-plugin-babel": "^5.2.1",
+ "eslint-plugin-import": "^2.14.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ }
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "eslint": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
+ "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "ajv": "^6.10.0",
+ "chalk": "^2.1.0",
+ "cross-spawn": "^6.0.5",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "eslint-scope": "^5.0.0",
+ "eslint-utils": "^1.4.3",
+ "eslint-visitor-keys": "^1.1.0",
+ "espree": "^6.1.2",
+ "esquery": "^1.0.1",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.0.0",
+ "globals": "^12.1.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "inquirer": "^7.0.0",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.3.0",
+ "lodash": "^4.17.14",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.8.3",
+ "progress": "^2.0.0",
+ "regexpp": "^2.0.1",
+ "semver": "^6.1.2",
+ "strip-ansi": "^5.2.0",
+ "strip-json-comments": "^3.0.1",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ }
+ },
+ "eslint-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+ "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
+ "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.1.1",
+ "acorn-jsx": "^5.2.0",
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+ "dev": true
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "globals": {
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.8.1"
+ }
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "regexpp": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-config-node": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-node/-/eslint-config-node-4.1.0.tgz",
+ "integrity": "sha512-Wz17xV5O2WFG8fGdMYEBdbiL6TL7YNJSJvSX9V4sXQownewfYmoqlly7wxqLkOUv/57pq6LnnotMiQQrrPjCqQ==",
+ "dev": true,
+ "requires": {
+ "eslint": "^6.8.0",
+ "eslint-config-esnext": "^4.1.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ }
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "eslint": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
+ "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "ajv": "^6.10.0",
+ "chalk": "^2.1.0",
+ "cross-spawn": "^6.0.5",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "eslint-scope": "^5.0.0",
+ "eslint-utils": "^1.4.3",
+ "eslint-visitor-keys": "^1.1.0",
+ "espree": "^6.1.2",
+ "esquery": "^1.0.1",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.0.0",
+ "globals": "^12.1.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "inquirer": "^7.0.0",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.3.0",
+ "lodash": "^4.17.14",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.8.3",
+ "progress": "^2.0.0",
+ "regexpp": "^2.0.1",
+ "semver": "^6.1.2",
+ "strip-ansi": "^5.2.0",
+ "strip-json-comments": "^3.0.1",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ }
+ },
+ "eslint-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+ "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
+ "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.1.1",
+ "acorn-jsx": "^5.2.0",
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+ "dev": true
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "globals": {
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.8.1"
+ }
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "regexpp": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-config-prettier": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
+ "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
+ "dev": true
+ },
+ "eslint-import-resolver-node": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
+ "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.2.7",
+ "resolve": "^1.20.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ }
+ }
+ },
+ "eslint-module-utils": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz",
+ "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.2.7",
+ "find-up": "^2.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-babel": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz",
+ "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==",
+ "dev": true,
+ "requires": {
+ "eslint-rule-composer": "^0.3.0"
+ }
+ },
+ "eslint-plugin-es": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz",
+ "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==",
+ "dev": true,
+ "requires": {
+ "eslint-utils": "^2.0.0",
+ "regexpp": "^3.0.0"
+ },
+ "dependencies": {
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-import": {
+ "version": "2.26.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
+ "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.4",
+ "array.prototype.flat": "^1.2.5",
+ "debug": "^2.6.9",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-module-utils": "^2.7.3",
+ "has": "^1.0.3",
+ "is-core-module": "^2.8.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.values": "^1.1.5",
+ "resolve": "^1.22.0",
+ "tsconfig-paths": "^3.14.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-jsx-a11y": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.0.tgz",
+ "integrity": "sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "aria-query": "^4.2.2",
+ "array-includes": "^3.1.5",
+ "ast-types-flow": "^0.0.7",
+ "axe-core": "^4.4.2",
+ "axobject-query": "^2.2.0",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^3.3.1",
+ "language-tags": "^1.0.5",
+ "minimatch": "^3.1.2",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-node": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz",
+ "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==",
+ "dev": true,
+ "requires": {
+ "eslint-plugin-es": "^3.0.0",
+ "eslint-utils": "^2.0.0",
+ "ignore": "^5.1.1",
+ "minimatch": "^3.0.4",
+ "resolve": "^1.10.1",
+ "semver": "^6.1.0"
+ },
+ "dependencies": {
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ },
+ "ignore": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-prettier": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
+ "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
+ "dev": true,
+ "requires": {
+ "prettier-linter-helpers": "^1.0.0"
+ }
+ },
+ "eslint-plugin-react": {
+ "version": "7.30.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz",
+ "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.5",
+ "array.prototype.flatmap": "^1.3.0",
+ "doctrine": "^2.1.0",
+ "estraverse": "^5.3.0",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.5",
+ "object.fromentries": "^2.0.5",
+ "object.hasown": "^1.1.1",
+ "object.values": "^1.1.5",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.3",
+ "semver": "^6.3.0",
+ "string.prototype.matchall": "^4.0.7"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
+ "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-react-hooks": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
+ "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
+ "dev": true
+ },
+ "eslint-rule-composer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
+ "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
+ "dev": true
+ },
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -6667,11 +7374,62 @@
"estraverse": "^4.1.1"
}
},
+ "eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "dev": true
+ },
+ "espree": {
+ "version": "9.3.2",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
+ "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.7.1",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
+ "esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ }
+ }
+ },
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -6723,13 +7481,10 @@
"dev": true
},
"eventsource": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
- "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
- "dev": true,
- "requires": {
- "original": "^1.0.0"
- }
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
+ "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
+ "dev": true
},
"execa": {
"version": "0.7.0",
@@ -6755,6 +7510,11 @@
"which": "^1.2.9"
}
},
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ=="
+ },
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
@@ -6771,11 +7531,6 @@
}
}
},
- "exenv": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
- "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
- },
"exeq": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/exeq/-/exeq-2.4.0.tgz",
@@ -6851,11 +7606,6 @@
"is-extendable": "^0.1.0"
}
},
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
- },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -6869,46 +7619,47 @@
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"express": {
- "version": "4.17.3",
- "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
- "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+ "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.19.2",
+ "body-parser": "1.20.0",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.4.2",
+ "cookie": "0.5.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
- "depd": "~1.1.2",
+ "depd": "2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "~1.1.2",
+ "finalhandler": "1.2.0",
"fresh": "0.5.2",
+ "http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
- "on-finished": "~2.3.0",
+ "on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.7",
- "qs": "6.9.7",
+ "qs": "6.10.3",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
- "send": "0.17.2",
- "serve-static": "1.14.2",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
"setprototypeof": "1.2.0",
- "statuses": "~1.5.0",
+ "statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"dependencies": {
"cookie": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
- "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
},
"debug": {
"version": "2.6.9",
@@ -6921,12 +7672,15 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"qs": {
- "version": "6.9.7",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
- "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw=="
+ "version": "6.10.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+ "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
},
"safe-buffer": {
"version": "5.2.1",
@@ -6944,11 +7698,11 @@
}
},
"express-session": {
- "version": "1.17.2",
- "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz",
- "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==",
+ "version": "1.17.3",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
+ "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
"requires": {
- "cookie": "0.4.1",
+ "cookie": "0.4.2",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~2.0.0",
@@ -6958,6 +7712,11 @@
"uid-safe": "~2.1.5"
},
"dependencies": {
+ "cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -6966,15 +7725,10 @@
"ms": "2.0.0"
}
},
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
- },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"safe-buffer": {
"version": "5.2.1",
@@ -6998,18 +7752,18 @@
"integrity": "sha1-IgMoRpoY31rWFeK3oM6ZXxf7ru8="
},
"ext": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
- "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz",
+ "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==",
"dev": true,
"requires": {
- "type": "^2.0.0"
+ "type": "^2.5.0"
},
"dependencies": {
"type": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
- "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz",
+ "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==",
"dev": true
}
}
@@ -7026,6 +7780,38 @@
"requires": {
"assign-symbols": "^1.0.0",
"is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "external-editor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+ "dev": true,
+ "requires": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ },
+ "dependencies": {
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ }
}
},
"extglob": {
@@ -7084,11 +7870,6 @@
"is-data-descriptor": "^1.0.0",
"kind-of": "^6.0.2"
}
- },
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
}
}
},
@@ -7104,20 +7885,25 @@
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"get-stream": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
- "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"requires": {
"pump": "^3.0.0"
}
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
@@ -7127,9 +7913,15 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
- "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "fast-diff": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+ "dev": true
},
"fast-json-stable-stringify": {
"version": "2.1.0",
@@ -7143,9 +7935,9 @@
"dev": true
},
"fast-text-encoding": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz",
- "integrity": "sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz",
+ "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig=="
},
"fastest-levenshtein": {
"version": "1.0.12",
@@ -7162,9 +7954,9 @@
}
},
"fbjs": {
- "version": "0.8.17",
- "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
- "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
+ "version": "0.8.18",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz",
+ "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==",
"requires": {
"core-js": "^1.0.0",
"isomorphic-fetch": "^2.1.1",
@@ -7172,7 +7964,7 @@
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
- "ua-parser-js": "^0.7.18"
+ "ua-parser-js": "^0.7.30"
}
},
"fd-slicer": {
@@ -7183,6 +7975,32 @@
"pend": "~1.2.0"
}
},
+ "ffmpeg": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz",
+ "integrity": "sha1-HEYN+OfaUSf2LO70v6BsWciWMMs=",
+ "requires": {
+ "when": ">= 0.0.1"
+ }
+ },
+ "figures": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
+ "file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^3.0.4"
+ }
+ },
"file-loader": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz",
@@ -7191,19 +8009,6 @@
"requires": {
"loader-utils": "^1.0.2",
"schema-utils": "^1.0.0"
- },
- "dependencies": {
- "schema-utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
- "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
- "dev": true,
- "requires": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- }
- }
}
},
"file-saver": {
@@ -7235,11 +8040,6 @@
"requires": {
"is-extendable": "^0.1.0"
}
- },
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
}
}
},
@@ -7249,16 +8049,16 @@
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
},
"finalhandler": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
- "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
- "on-finished": "~2.3.0",
+ "on-finished": "2.4.1",
"parseurl": "~1.3.3",
- "statuses": "~1.5.0",
+ "statuses": "2.0.1",
"unpipe": "~1.0.0"
},
"dependencies": {
@@ -7273,7 +8073,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}
}
},
@@ -7294,6 +8094,75 @@
"commondir": "^1.0.1",
"make-dir": "^1.0.0",
"pkg-dir": "^2.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "dev": true,
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
+ "dev": true
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ }
}
},
"find-in-files": {
@@ -7306,9 +8175,9 @@
}
},
"find-parent-dir": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz",
- "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ="
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz",
+ "integrity": "sha512-o4UcykWV/XN9wm+jMEtWLPlV8RXCZnMhQI6F6OdHeSez7iiJWePw8ijOlskJZMsaQoGR/b7dH6lO02HhaTN7+A=="
},
"find-root": {
"version": "1.1.0",
@@ -7329,13 +8198,29 @@
"integrity": "sha512-Md3b3ReA/qJwwYvKXeHpOV1fhPqwhJ9/29zc9lfi8ijyg00J0S9C7+5XMZDFhlDAKbwOvVgBxVIG1EHoMSC3vw=="
},
"flat": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
- "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz",
+ "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==",
"requires": {
"is-buffer": "~2.0.3"
}
},
+ "flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "requires": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "flatted": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz",
+ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==",
+ "dev": true
+ },
"flexlayout-react": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/flexlayout-react/-/flexlayout-react-0.3.11.tgz",
@@ -7378,9 +8263,35 @@
}
},
"follow-redirects": {
- "version": "1.12.1",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
- "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg=="
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ }
+ }
+ },
+ "for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "requires": {
+ "is-callable": "^1.1.3"
+ }
},
"for-each-property": {
"version": "0.0.4",
@@ -7403,11 +8314,6 @@
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
},
- "foreach": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
- "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
- },
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -7427,13 +8333,20 @@
"semver": "^5.6.0",
"tapable": "^1.0.0",
"worker-rpc": "^0.1.0"
+ },
+ "dependencies": {
+ "tapable": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+ "dev": true
+ }
}
},
"form-data": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
- "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
- "dev": true,
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
@@ -7570,11 +8483,14 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
+ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
+ "optional": true,
+ "requires": {
+ "bindings": "^1.5.0",
+ "nan": "^2.12.1"
+ }
},
"fstream": {
"version": "1.0.12",
@@ -7620,50 +8536,75 @@
"interval-arithmetic-eval": "^0.4.7"
}
},
+ "function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ }
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "dev": true
+ },
+ "functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
+ },
"gauge": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
+ "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"requires": {
- "aproba": "^1.0.3",
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.0",
- "object-assign": "^4.1.0",
+ "has-unicode": "^2.0.1",
+ "object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wide-align": "^1.1.0"
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.2"
},
"dependencies": {
"ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "requires": {
- "number-is-nan": "^1.0.0"
- }
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"requires": {
- "ansi-regex": "^2.0.0"
+ "ansi-regex": "^5.0.1"
}
}
}
@@ -7681,14 +8622,17 @@
},
"dependencies": {
"is-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
- "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
},
"node-fetch": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
- "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "requires": {
+ "whatwg-url": "^5.0.0"
+ }
}
}
},
@@ -7720,13 +8664,13 @@
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE="
},
"get-intrinsic": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
- "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
+ "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
- "has-symbols": "^1.0.1"
+ "has-symbols": "^1.0.3"
}
},
"get-node-dimensions": {
@@ -7745,9 +8689,9 @@
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g="
},
"get-stream": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
- "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
},
"get-symbol-description": {
"version": "1.0.0",
@@ -7777,14 +8721,14 @@
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
},
"glob": {
- "version": "7.1.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
- "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
- "minimatch": "^3.0.4",
+ "minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
@@ -7850,13 +8794,36 @@
}
},
"globule": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz",
- "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz",
+ "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==",
"requires": {
"glob": "~7.1.1",
- "lodash": "~4.17.12",
+ "lodash": "~4.17.10",
"minimatch": "~3.0.2"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
+ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
+ "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
}
},
"golden-layout": {
@@ -7867,6 +8834,14 @@
"jquery": "*"
}
},
+ "good-listener": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+ "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+ "requires": {
+ "delegate": "^3.1.2"
+ }
+ },
"google-auth-library": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.2.6.tgz",
@@ -7880,6 +8855,21 @@
"gtoken": "^3.0.0",
"jws": "^3.1.5",
"lru-cache": "^5.0.0"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "requires": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ }
}
},
"google-maps-react": {
@@ -7888,11 +8878,11 @@
"integrity": "sha512-M8Eo9WndfQEfxcmm6yRq03qdJgw1x6rQmJ9DN+a+xPQ3K7yNDGkVDbinrf4/8vcox7nELbeopbm4bpefKewWfQ=="
},
"google-p12-pem": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz",
- "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.5.tgz",
+ "integrity": "sha512-7RLkxwSsMsYh9wQ5Vb2zRtkAHvqPvfoMGag+nugl1noYO7gf0844Yr9TIFA5NEBMAeVt2Z+Imu7CQMp3oNatzQ==",
"requires": {
- "node-forge": "^0.9.0"
+ "node-forge": "^0.10.0"
}
},
"googleapis": {
@@ -7918,9 +8908,12 @@
},
"dependencies": {
"qs": {
- "version": "6.9.3",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
- "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw=="
+ "version": "6.10.5",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.5.tgz",
+ "integrity": "sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
}
}
},
@@ -7935,9 +8928,9 @@
}
},
"got": {
- "version": "12.0.2",
- "resolved": "https://registry.npmjs.org/got/-/got-12.0.2.tgz",
- "integrity": "sha512-Zi4yHiqCgaorUbknr/RHFBsC3XqjSodaw0F3qxlqAqyj+OGYZl37/uy01R0qz++KANKQYdY5FHJ0okXZpEzwWQ==",
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz",
+ "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==",
"requires": {
"@sindresorhus/is": "^4.6.0",
"@szmarczak/http-timer": "^5.0.1",
@@ -7962,11 +8955,6 @@
"mimic-response": "^3.1.0"
}
},
- "get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
- },
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -7975,9 +8963,9 @@
}
},
"graceful-fs": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
- "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
+ "version": "4.2.10",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
},
"growl": {
"version": "1.10.5",
@@ -7996,9 +8984,9 @@
},
"dependencies": {
"mime": {
- "version": "2.4.4",
- "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
- "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
}
}
},
@@ -8019,11 +9007,11 @@
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
- "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"requires": {
- "ajv": "^6.5.5",
+ "ajv": "^6.12.3",
"har-schema": "^2.0.0"
}
},
@@ -8051,9 +9039,9 @@
}
},
"has-bigints": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
- "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
},
"has-binary2": {
"version": "1.0.3",
@@ -8066,24 +9054,32 @@
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
- "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+ "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
}
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
- "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
+ "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
+ "has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "requires": {
+ "get-intrinsic": "^1.1.1"
+ }
+ },
"has-symbols": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
- "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"has-tostringtag": {
"version": "1.0.0",
@@ -8091,13 +9087,6 @@
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"requires": {
"has-symbols": "^1.0.2"
- },
- "dependencies": {
- "has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
- }
}
},
"has-unicode": {
@@ -8153,9 +9142,9 @@
}
},
"hosted-git-info": {
- "version": "2.8.8",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
- "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
},
"howler": {
"version": "2.2.3",
@@ -8226,63 +9215,6 @@
"param-case": "^3.0.4",
"relateurl": "^0.2.7",
"terser": "^5.10.0"
- },
- "dependencies": {
- "acorn": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
- "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
- },
- "clean-css": {
- "version": "5.2.4",
- "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.4.tgz",
- "integrity": "sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg==",
- "requires": {
- "source-map": "~0.6.0"
- }
- },
- "commander": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
- "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- },
- "source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "requires": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "terser": {
- "version": "5.12.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
- "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
- "requires": {
- "acorn": "^8.5.0",
- "commander": "^2.20.0",
- "source-map": "~0.7.2",
- "source-map-support": "~0.5.20"
- },
- "dependencies": {
- "commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
- },
- "source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
- }
- }
- }
}
},
"html-to-image": {
@@ -8311,13 +9243,6 @@
"lodash": "^4.17.21",
"pretty-error": "^4.0.0",
"tapable": "^2.0.0"
- },
- "dependencies": {
- "tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
- }
}
},
"htmlparser2": {
@@ -8354,14 +9279,14 @@
"dev": true
},
"http-errors": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
- "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"requires": {
- "depd": "~1.1.2",
+ "depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
- "statuses": ">= 1.5.0 < 2",
+ "statuses": "2.0.1",
"toidentifier": "1.0.1"
}
},
@@ -8405,9 +9330,9 @@
}
},
"http2-wrapper": {
- "version": "2.1.10",
- "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.10.tgz",
- "integrity": "sha512-QHgsdYkieKp+6JbXP25P+tepqiHYd+FVnDwXpxi/BlUcoIB0nsmTOymTNvETuTO+pDuwcSklPE72VR3DqV+Haw==",
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz",
+ "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==",
"requires": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.2.0"
@@ -8424,38 +9349,38 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
"https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"requires": {
"agent-base": "6",
"debug": "4"
},
"dependencies": {
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
- "human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
- },
"hyntax": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/hyntax/-/hyntax-1.1.9.tgz",
"integrity": "sha512-xjxyDLbVDdLgjPnl4NM+Iu6il3UPmk6PNCBXruQKeuKDc/HtaZx1hk1AtMgw3vsn9YnLZRfoBpPxYMXcoT5KAA=="
},
"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=="
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
+ "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"i": {
"version": "0.3.7",
@@ -8463,11 +9388,11 @@
"integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q=="
},
"iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"requires": {
- "safer-buffer": ">= 2.1.2 < 3"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"icss-replace-symbols": {
@@ -8486,9 +9411,9 @@
}
},
"ieee754": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
- "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"iferr": {
"version": "0.1.5",
@@ -8507,6 +9432,29 @@
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk="
},
+ "iink-js": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/iink-js/-/iink-js-1.5.4.tgz",
+ "integrity": "sha512-WcjMmyXbSo4GY56AVVTKRyxlguh2KAGFyoH24+Zmm87ZWII12or2uLiovAyOK4IE+VVz7m0U5RMuv8i/RV/3Nw==",
+ "requires": {
+ "@babel/runtime": "^7.9.2",
+ "clipboard": "^1.7.1",
+ "crypto-js": "^3.3.0",
+ "d3-selection": "^1.4.1",
+ "json-css": "^1.5.6",
+ "lodash.merge": "^4.6.2",
+ "loglevel": "^1.6.8",
+ "perfect-scrollbar": "^1.5.0",
+ "uuid-js": "^0.7.5"
+ },
+ "dependencies": {
+ "d3-selection": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
+ "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
+ }
+ }
+ },
"image-data-uri": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/image-data-uri/-/image-data-uri-2.0.1.tgz",
@@ -8586,46 +9534,6 @@
"requires": {
"pkg-dir": "^4.2.0",
"resolve-cwd": "^3.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "requires": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "requires": {
- "p-locate": "^4.1.0"
- }
- },
- "p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "requires": {
- "p-limit": "^2.2.0"
- }
- },
- "path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
- },
- "pkg-dir": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
- "requires": {
- "find-up": "^4.0.0"
- }
- }
}
},
"imurmurhash": {
@@ -8646,16 +9554,10 @@
"repeating": "^2.0.0"
}
},
- "indexes-of": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
- "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
- "dev": true
- },
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
- "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
+ "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
},
"inflight": {
"version": "1.0.6",
@@ -8687,9 +9589,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
- "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"inline-style-prefixer": {
"version": "3.0.8",
@@ -8700,6 +9602,116 @@
"css-in-js-utils": "^2.0.0"
}
},
+ "inquirer": {
+ "version": "7.3.3",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
+ "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-width": "^3.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^3.0.0",
+ "lodash": "^4.17.19",
+ "mute-stream": "0.0.8",
+ "run-async": "^2.4.0",
+ "rxjs": "^6.6.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"inspect-function": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz",
@@ -8727,6 +9739,11 @@
"unpack-string": "0.0.2"
},
"dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
"magicli": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
@@ -8750,6 +9767,11 @@
"inspect-function": "^0.3.1"
},
"dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
"inspect-function": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.3.4.tgz",
@@ -8873,9 +9895,9 @@
}
},
"ip": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
- "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
+ "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==",
"dev": true
},
"ip-regex": {
@@ -8958,14 +9980,14 @@
}
},
"is-buffer": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
- "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
},
"is-callable": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
- "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
+ "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
},
"is-ci": {
"version": "1.2.1",
@@ -8975,6 +9997,14 @@
"ci-info": "^1.5.0"
}
},
+ "is-core-module": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
+ "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
@@ -8999,9 +10029,12 @@
}
},
"is-date-object": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
- "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
},
"is-descriptor": {
"version": "0.1.6",
@@ -9042,12 +10075,9 @@
}
},
"is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "requires": {
- "is-plain-object": "^2.0.4"
- }
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="
},
"is-extglob": {
"version": "2.1.1",
@@ -9073,9 +10103,9 @@
}
},
"is-glob": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
- "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"requires": {
"is-extglob": "^2.1.1"
}
@@ -9128,9 +10158,9 @@
}
},
"is-number-object": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
- "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
"requires": {
"has-tostringtag": "^1.0.0"
}
@@ -9193,11 +10223,12 @@
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
},
"is-regex": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
- "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"requires": {
- "has": "^1.0.3"
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
}
},
"is-retry-allowed": {
@@ -9214,9 +10245,12 @@
}
},
"is-shared-array-buffer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz",
- "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA=="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
},
"is-stream": {
"version": "1.1.0",
@@ -9232,105 +10266,23 @@
}
},
"is-symbol": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
- "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
"requires": {
- "has-symbols": "^1.0.1"
+ "has-symbols": "^1.0.2"
}
},
"is-typed-array": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz",
- "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==",
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz",
+ "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==",
"requires": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
- "es-abstract": "^1.18.5",
- "foreach": "^2.0.5",
+ "es-abstract": "^1.20.0",
+ "for-each": "^0.3.3",
"has-tostringtag": "^1.0.0"
- },
- "dependencies": {
- "es-abstract": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
- "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
- "requires": {
- "call-bind": "^1.0.2",
- "es-to-primitive": "^1.2.1",
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.1.1",
- "get-symbol-description": "^1.0.0",
- "has": "^1.0.3",
- "has-symbols": "^1.0.2",
- "internal-slot": "^1.0.3",
- "is-callable": "^1.2.4",
- "is-negative-zero": "^2.0.1",
- "is-regex": "^1.1.4",
- "is-shared-array-buffer": "^1.0.1",
- "is-string": "^1.0.7",
- "is-weakref": "^1.0.1",
- "object-inspect": "^1.11.0",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.2",
- "string.prototype.trimend": "^1.0.4",
- "string.prototype.trimstart": "^1.0.4",
- "unbox-primitive": "^1.0.1"
- }
- },
- "has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
- },
- "is-callable": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
- "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
- },
- "is-regex": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
- "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
- "requires": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- }
- },
- "object-inspect": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
- "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
- },
- "object.assign": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
- "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
- "requires": {
- "call-bind": "^1.0.0",
- "define-properties": "^1.1.3",
- "has-symbols": "^1.0.1",
- "object-keys": "^1.1.1"
- }
- },
- "string.prototype.trimend": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
- "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3"
- }
- },
- "string.prototype.trimstart": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
- "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3"
- }
- }
}
},
"is-typedarray": {
@@ -9338,6 +10290,11 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
+ "is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
+ },
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@@ -9352,9 +10309,9 @@
}
},
"is-what": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.10.0.tgz",
- "integrity": "sha512-U4RYCXNOmATQHlOPlOCHCfXyKEFIPqvyaKDqYRuLbD6EYKcTTfc3YXkAYjzOVxO3zt34L+Wh2feIyWrYiZ7kng=="
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
+ "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA=="
},
"is-windows": {
"version": "1.0.2",
@@ -9434,14 +10391,14 @@
}
},
"jquery": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
- "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
+ "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
},
"js-base64": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
- "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ=="
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
+ "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ=="
},
"js-datepicker": {
"version": "4.6.6",
@@ -9507,9 +10464,9 @@
},
"dependencies": {
"acorn": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
- "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"acorn-globals": {
@@ -9523,19 +10480,13 @@
},
"dependencies": {
"acorn": {
- "version": "6.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
- "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
+ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
"dev": true
}
}
},
- "parse5": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
- "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
- "dev": true
- },
"tough-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
@@ -9546,6 +10497,32 @@
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
+ },
+ "tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true
+ },
+ "whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "dev": true,
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
}
}
},
@@ -9555,11 +10532,11 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
},
"json-bigint": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz",
- "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=",
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz",
+ "integrity": "sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ==",
"requires": {
- "bignumber.js": "^7.0.0"
+ "bignumber.js": "^9.0.0"
}
},
"json-buffer": {
@@ -9567,6 +10544,11 @@
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
+ "json-css": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz",
+ "integrity": "sha1-65ZPg0ouTqobwvaY/12wB6JsfAA="
+ },
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@@ -9578,20 +10560,34 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
"jsondiffpatch": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz",
@@ -9610,18 +10606,18 @@
}
},
"jsonschema": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.0.tgz",
- "integrity": "sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw=="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
+ "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ=="
},
"jsprim": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
- "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+ "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
- "json-schema": "0.2.3",
+ "json-schema": "0.4.0",
"verror": "1.10.0"
}
},
@@ -9637,9 +10633,9 @@
},
"dependencies": {
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
}
}
},
@@ -9719,15 +10715,39 @@
"promise": "^7.0.1"
}
},
+ "jsx-ast-utils": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz",
+ "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.5",
+ "object.assign": "^4.1.2"
+ },
+ "dependencies": {
+ "object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ }
+ }
+ },
"jszip": {
- "version": "3.7.1",
- "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz",
- "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz",
+ "integrity": "sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q==",
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
- "set-immediate-shim": "~1.0.1"
+ "setimmediate": "^1.0.5"
},
"dependencies": {
"readable-stream": {
@@ -9776,9 +10796,9 @@
"integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew=="
},
"keycode": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
- "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
+ "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
},
"keygrip": {
"version": "1.1.0",
@@ -9789,10 +10809,11 @@
}
},
"keyv": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.1.1.tgz",
- "integrity": "sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.3.0.tgz",
+ "integrity": "sha512-C30Un9+63J0CsR7Wka5quXKqYZsT6dcRQ2aOwGcSc3RiQ4HGWpTAHlCA+puNfw2jA/s11EsxA1nCXgZRuRKMQQ==",
"requires": {
+ "compress-brotli": "^1.3.8",
"json-buffer": "3.0.1"
}
},
@@ -9815,6 +10836,21 @@
"graceful-fs": "^4.1.9"
}
},
+ "language-subtag-registry": {
+ "version": "0.3.22",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
+ "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==",
+ "dev": true
+ },
+ "language-tags": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz",
+ "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==",
+ "dev": true,
+ "requires": {
+ "language-subtag-registry": "~0.3.2"
+ }
+ },
"latest-version": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
@@ -9829,9 +10865,9 @@
"integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
},
"lazystream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
- "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
+ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"requires": {
"readable-stream": "^2.0.5"
},
@@ -9894,18 +10930,13 @@
"requires": {
"error-ex": "^1.2.0"
}
- },
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
}
}
},
"loader-runner": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
- "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true
},
"loader-utils": {
@@ -9916,16 +10947,6 @@
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
- },
- "dependencies": {
- "json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
- "requires": {
- "minimist": "^1.2.0"
- }
- }
}
},
"locate-path": {
@@ -9998,7 +11019,7 @@
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
"lodash.isplainobject": {
"version": "4.0.6",
@@ -10047,8 +11068,7 @@
"loglevel": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz",
- "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==",
- "dev": true
+ "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA=="
},
"loglevelnext": {
"version": "1.0.5",
@@ -10096,13 +11116,6 @@
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
"requires": {
"tslib": "^2.0.3"
- },
- "dependencies": {
- "tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
- }
}
},
"lowercase-keys": {
@@ -10111,11 +11124,11 @@
"integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="
},
"lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
- "yallist": "^3.0.2"
+ "yallist": "^4.0.0"
}
},
"magicli": {
@@ -10170,17 +11183,17 @@
}
},
"make-dir": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
- "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"requires": {
- "pify": "^3.0.0"
+ "semver": "^6.0.0"
},
"dependencies": {
- "pify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
- "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
@@ -10257,7 +11270,7 @@
"mathquill": {
"version": "0.10.1-a",
"resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz",
- "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=",
+ "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==",
"requires": {
"jquery": "^1.12.3"
},
@@ -10265,7 +11278,7 @@
"jquery": {
"version": "1.12.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz",
- "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw="
+ "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg=="
}
}
},
@@ -10280,17 +11293,17 @@
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"memfs": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
- "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==",
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.4.tgz",
+ "integrity": "sha512-W4gHNUE++1oSJVn8Y68jPXi+mkx3fXR5ITE/Ubz6EQ3xRpCN5k2CQ4AUR8094Z7211F876TyoBACGsIveqgiGA==",
"requires": {
"fs-monkey": "1.0.3"
}
},
"memoize-one": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
- "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"memory-fs": {
"version": "0.5.0",
@@ -10363,7 +11376,8 @@
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
},
"methods": {
"version": "1.1.2",
@@ -10402,22 +11416,23 @@
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
- "version": "1.44.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
- "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
- "version": "2.1.27",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
- "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
- "mime-db": "1.44.0"
+ "mime-db": "1.52.0"
}
},
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true
},
"mimic-response": {
"version": "2.1.0",
@@ -10431,17 +11446,17 @@
"dev": true
},
"minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"minipass": {
"version": "3.1.6",
@@ -10449,13 +11464,6 @@
"integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==",
"requires": {
"yallist": "^4.0.0"
- },
- "dependencies": {
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
- }
}
},
"minizlib": {
@@ -10465,13 +11473,6 @@
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
- },
- "dependencies": {
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
- }
}
},
"mississippi": {
@@ -10511,6 +11512,16 @@
"requires": {
"for-in": "^1.0.2",
"is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
}
},
"mkdirp": {
@@ -10522,9 +11533,9 @@
}
},
"mkdirp-classic": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz",
- "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g=="
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"mobile-detect": {
"version": "1.4.5",
@@ -10609,6 +11620,15 @@
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
"dev": true
},
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
@@ -10721,15 +11741,6 @@
"sliced": "1.0.1"
},
"dependencies": {
- "@types/mongodb": {
- "version": "3.6.20",
- "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz",
- "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==",
- "requires": {
- "@types/bson": "*",
- "@types/node": "*"
- }
- },
"bson": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
@@ -10771,6 +11782,12 @@
"run-queue": "^1.0.3"
},
"dependencies": {
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -10845,10 +11862,16 @@
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
"dev": true
},
+ "mute-stream": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+ "dev": true
+ },
"nan": {
- "version": "2.14.1",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
- "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
+ "version": "2.16.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz",
+ "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA=="
},
"nanoid": {
"version": "2.1.11",
@@ -10883,21 +11906,27 @@
"resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz",
"integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck="
},
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
"negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"neo-async": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
- "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"next-tick": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
- "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
+ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"nextafter": {
"version": "1.0.0",
@@ -10920,19 +11949,12 @@
"requires": {
"lower-case": "^2.0.2",
"tslib": "^2.0.3"
- },
- "dependencies": {
- "tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
- }
}
},
"node-abi": {
- "version": "2.16.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.16.0.tgz",
- "integrity": "sha512-+sa0XNlWDA6T+bDLmkCUYn6W5k5W6BPRL6mqzSCs6H/xUgtl4D5x2fORKDzopKiU6wsyn/+wXlRXwXeSp+mtoA==",
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz",
+ "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==",
"requires": {
"semver": "^5.4.1"
}
@@ -10961,9 +11983,9 @@
}
},
"node-forge": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz",
- "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ=="
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
+ "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
},
"node-gyp": {
"version": "3.8.0",
@@ -10984,6 +12006,48 @@
"which": "1"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ },
+ "are-we-there-yet": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
+ "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
@@ -10992,6 +12056,31 @@
"abbrev": "1"
}
},
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -11005,6 +12094,24 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
},
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
"tar": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
@@ -11018,9 +12125,9 @@
}
},
"node-releases": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz",
- "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg=="
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
+ "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q=="
},
"node-sass": {
"version": "4.14.1",
@@ -11056,6 +12163,20 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ },
+ "are-we-there-yet": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
+ "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
@@ -11068,11 +12189,69 @@
"supports-color": "^2.0.0"
}
},
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
},
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -11163,9 +12342,9 @@
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
},
"npm": {
- "version": "6.14.16",
- "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.16.tgz",
- "integrity": "sha512-LMiLGYsVNJfVPlQg7v2NYjG7iRIapcLv+oMunlq7fkXVx0BATCjRu7XyWl0G+iuZzHy4CjtM32QB8ox8juTgaw==",
+ "version": "6.14.17",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.17.tgz",
+ "integrity": "sha512-CxEDn1ydVRPDl4tHrlnq+WevYAhv4GF2AEHzJKQ4prZDZ96IS3Uo6t0Sy6O9kB6XzqkI+J00WfYCqqk0p6IJ1Q==",
"requires": {
"JSONStream": "^1.3.5",
"abbrev": "~1.1.1",
@@ -11302,59 +12481,70 @@
},
"abbrev": {
"version": "1.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"agent-base": {
"version": "4.3.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"requires": {
"es6-promisify": "^5.0.0"
}
},
"agentkeepalive": {
"version": "3.5.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
"requires": {
"humanize-ms": "^1.2.1"
}
},
"ansi-align": {
"version": "2.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
"requires": {
"string-width": "^2.0.0"
}
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"ansi-styles": {
"version": "3.2.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"ansicolors": {
"version": "0.3.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk="
},
"ansistyles": {
"version": "0.1.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk="
},
"aproba": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"archy": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA="
},
"are-we-there-yet": {
"version": "1.1.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
@@ -11362,7 +12552,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -11375,7 +12566,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -11384,38 +12576,46 @@
},
"asap": {
"version": "2.0.6",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"asn1": {
"version": "0.2.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"requires": {
"safer-buffer": "~2.1.0"
}
},
"assert-plus": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"asynckit": {
"version": "0.4.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"aws-sign2": {
"version": "0.7.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.8.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"optional": true,
"requires": {
"tweetnacl": "^0.14.3"
@@ -11423,7 +12623,8 @@
},
"bin-links": {
"version": "1.1.8",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==",
"requires": {
"bluebird": "^3.5.3",
"cmd-shim": "^3.0.0",
@@ -11435,11 +12636,13 @@
},
"bluebird": {
"version": "3.5.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
},
"boxen": {
"version": "1.3.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
"requires": {
"ansi-align": "^2.0.0",
"camelcase": "^4.0.0",
@@ -11452,7 +12655,8 @@
},
"brace-expansion": {
"version": "1.1.11",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -11460,23 +12664,28 @@
},
"buffer-from": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA=="
},
"builtins": {
"version": "1.0.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og="
},
"byline": {
"version": "5.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE="
},
"byte-size": {
"version": "5.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw=="
},
"cacache": {
"version": "12.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
"requires": {
"bluebird": "^3.5.5",
"chownr": "^1.1.1",
@@ -11497,23 +12706,28 @@
},
"call-limit": {
"version": "1.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ=="
},
"camelcase": {
"version": "4.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
},
"capture-stack-trace": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0="
},
"caseless": {
"version": "0.12.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"chalk": {
"version": "2.4.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -11522,26 +12736,31 @@
},
"chownr": {
"version": "1.1.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"ci-info": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
},
"cidr-regex": {
"version": "2.0.10",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==",
"requires": {
"ip-regex": "^2.1.0"
}
},
"cli-boxes": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
},
"cli-columns": {
"version": "3.1.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=",
"requires": {
"string-width": "^2.0.0",
"strip-ansi": "^3.0.1"
@@ -11549,7 +12768,8 @@
},
"cli-table3": {
"version": "0.5.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
"requires": {
"colors": "^1.1.2",
"object-assign": "^4.1.0",
@@ -11558,7 +12778,8 @@
},
"cliui": {
"version": "5.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
@@ -11566,16 +12787,19 @@
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
- "bundled": true
+ "version": "4.1.1",
+ "resolved": false,
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"string-width": {
"version": "3.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -11584,7 +12808,8 @@
},
"strip-ansi": {
"version": "5.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -11593,11 +12818,13 @@
},
"clone": {
"version": "1.0.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
},
"cmd-shim": {
"version": "3.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==",
"requires": {
"graceful-fs": "^4.1.2",
"mkdirp": "~0.5.0"
@@ -11605,27 +12832,32 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"color-convert": {
"version": "1.9.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"requires": {
"color-name": "^1.1.1"
}
},
"color-name": {
"version": "1.1.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"colors": {
"version": "1.3.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
"optional": true
},
"columnify": {
"version": "1.5.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
"requires": {
"strip-ansi": "^3.0.0",
"wcwidth": "^1.0.0"
@@ -11633,18 +12865,21 @@
},
"combined-stream": {
"version": "1.0.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"concat-map": {
"version": "0.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"concat-stream": {
"version": "1.6.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
@@ -11654,7 +12889,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -11667,7 +12903,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -11676,7 +12913,8 @@
},
"config-chain": {
"version": "1.1.12",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
"requires": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
@@ -11684,7 +12922,8 @@
},
"configstore": {
"version": "3.1.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==",
"requires": {
"dot-prop": "^4.2.1",
"graceful-fs": "^4.1.2",
@@ -11696,11 +12935,13 @@
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"copy-concurrently": {
"version": "1.0.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
"requires": {
"aproba": "^1.1.1",
"fs-write-stream-atomic": "^1.0.8",
@@ -11712,28 +12953,33 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"iferr": {
"version": "0.1.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
}
}
},
"core-util-is": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"create-error-class": {
"version": "3.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
"requires": {
"capture-stack-trace": "^1.0.0"
}
},
"cross-spawn": {
"version": "5.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"requires": {
"lru-cache": "^4.0.1",
"shebang-command": "^1.2.0",
@@ -11742,7 +12988,8 @@
"dependencies": {
"lru-cache": {
"version": "4.1.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
@@ -11750,87 +12997,104 @@
},
"yallist": {
"version": "2.1.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}
}
},
"crypto-random-string": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
},
"cyclist": {
"version": "0.2.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
},
"dashdash": {
"version": "1.14.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "^1.0.0"
}
},
"debug": {
"version": "3.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
},
"dependencies": {
"ms": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"debuglog": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI="
},
"decamelize": {
"version": "1.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"decode-uri-component": {
"version": "0.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"deep-extend": {
"version": "0.6.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
"defaults": {
"version": "1.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"requires": {
"clone": "^1.0.2"
}
},
"define-properties": {
"version": "1.1.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
"object-keys": "^1.0.12"
}
},
"delayed-stream": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"detect-indent": {
"version": "5.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50="
},
"detect-newline": {
"version": "2.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I="
},
"dezalgo": {
"version": "1.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
"requires": {
"asap": "^2.0.0",
"wrappy": "1"
@@ -11838,22 +13102,26 @@
},
"dot-prop": {
"version": "4.2.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
"requires": {
"is-obj": "^1.0.0"
}
},
"dotenv": {
"version": "5.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow=="
},
"duplexer3": {
"version": "0.1.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
"duplexify": {
"version": "3.6.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
"requires": {
"end-of-stream": "^1.0.0",
"inherits": "^2.0.1",
@@ -11863,7 +13131,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -11876,7 +13145,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -11885,7 +13155,8 @@
},
"ecc-jsbn": {
"version": "0.1.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"optional": true,
"requires": {
"jsbn": "~0.1.0",
@@ -11894,44 +13165,52 @@
},
"editor": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I="
},
"emoji-regex": {
"version": "7.0.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
},
"encoding": {
"version": "0.1.12",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"requires": {
"iconv-lite": "~0.4.13"
}
},
"end-of-stream": {
"version": "1.4.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"requires": {
"once": "^1.4.0"
}
},
"env-paths": {
"version": "2.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA=="
},
"err-code": {
"version": "1.1.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA="
},
"errno": {
"version": "0.1.7",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
"requires": {
"prr": "~1.0.1"
}
},
"es-abstract": {
"version": "1.12.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
"requires": {
"es-to-primitive": "^1.1.1",
"function-bind": "^1.1.1",
@@ -11942,7 +13221,8 @@
},
"es-to-primitive": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
@@ -11951,22 +13231,26 @@
},
"es6-promise": {
"version": "4.2.8",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"es6-promisify": {
"version": "5.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"requires": {
"es6-promise": "^4.0.3"
}
},
"escape-string-regexp": {
"version": "1.0.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"execa": {
"version": "0.7.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"requires": {
"cross-spawn": "^5.0.1",
"get-stream": "^3.0.0",
@@ -11979,33 +13263,40 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
}
}
},
"extend": {
"version": "3.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extsprintf": {
"version": "1.3.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"figgy-pudding": {
"version": "3.5.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w=="
},
"find-npm-prefix": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA=="
},
"flush-write-stream": {
"version": "1.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
"requires": {
"inherits": "^2.0.1",
"readable-stream": "^2.0.4"
@@ -12013,7 +13304,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -12026,7 +13318,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -12035,11 +13328,13 @@
},
"forever-agent": {
"version": "0.6.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "1.0.6",
@@ -12048,7 +13343,8 @@
},
"from2": {
"version": "2.3.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
"requires": {
"inherits": "^2.0.1",
"readable-stream": "^2.0.0"
@@ -12056,7 +13352,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -12069,7 +13366,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -12078,14 +13376,16 @@
},
"fs-minipass": {
"version": "1.2.7",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"requires": {
"minipass": "^2.6.0"
},
"dependencies": {
"minipass": {
"version": "2.9.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -12095,7 +13395,8 @@
},
"fs-vacuum": {
"version": "1.2.10",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=",
"requires": {
"graceful-fs": "^4.1.2",
"path-is-inside": "^1.0.1",
@@ -12104,7 +13405,8 @@
},
"fs-write-stream-atomic": {
"version": "1.0.10",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
"requires": {
"graceful-fs": "^4.1.2",
"iferr": "^0.1.5",
@@ -12114,11 +13416,13 @@
"dependencies": {
"iferr": {
"version": "0.1.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
},
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -12131,7 +13435,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -12140,15 +13445,18 @@
},
"fs.realpath": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"function-bind": {
"version": "1.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"gauge": {
"version": "2.7.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
@@ -12162,11 +13470,13 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"string-width": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -12177,11 +13487,13 @@
},
"genfun": {
"version": "5.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA=="
},
"gentle-fs": {
"version": "2.3.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==",
"requires": {
"aproba": "^1.1.2",
"chownr": "^1.1.2",
@@ -12198,35 +13510,41 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"iferr": {
"version": "0.1.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
}
}
},
"get-caller-file": {
"version": "2.0.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-stream": {
"version": "4.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"requires": {
"pump": "^3.0.0"
}
},
"getpass": {
"version": "0.1.7",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "^1.0.0"
}
},
"glob": {
"version": "7.1.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -12238,14 +13556,16 @@
},
"global-dirs": {
"version": "0.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
"requires": {
"ini": "^1.3.4"
}
},
"got": {
"version": "6.7.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
"requires": {
"create-error-class": "^3.0.0",
"duplexer3": "^0.1.4",
@@ -12262,21 +13582,25 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
}
}
},
"graceful-fs": {
"version": "4.2.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
},
"har-schema": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"requires": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
@@ -12284,7 +13608,8 @@
"dependencies": {
"ajv": {
"version": "6.12.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -12294,44 +13619,53 @@
},
"fast-deep-equal": {
"version": "3.1.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"json-schema-traverse": {
"version": "0.4.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
}
}
},
"has": {
"version": "1.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "3.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbols": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
},
"has-unicode": {
"version": "2.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"hosted-git-info": {
"version": "2.8.9",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
},
"http-cache-semantics": {
"version": "3.8.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w=="
},
"http-proxy-agent": {
"version": "2.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
"requires": {
"agent-base": "4",
"debug": "3.1.0"
@@ -12339,7 +13673,8 @@
},
"http-signature": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -12348,7 +13683,8 @@
},
"https-proxy-agent": {
"version": "2.2.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
"requires": {
"agent-base": "^4.3.0",
"debug": "^3.1.0"
@@ -12356,44 +13692,52 @@
},
"humanize-ms": {
"version": "1.2.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
"requires": {
"ms": "^2.0.0"
}
},
"iconv-lite": {
"version": "0.4.23",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"iferr": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg=="
},
"ignore-walk": {
"version": "3.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
"requires": {
"minimatch": "^3.0.4"
}
},
"import-lazy": {
"version": "2.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM="
},
"imurmurhash": {
"version": "0.1.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
},
"infer-owner": {
"version": "1.0.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="
},
"inflight": {
"version": "1.0.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -12401,15 +13745,18 @@
},
"inherits": {
"version": "2.0.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.8",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"init-package-json": {
"version": "1.10.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==",
"requires": {
"glob": "^7.1.1",
"npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0",
@@ -12423,50 +13770,59 @@
},
"ip": {
"version": "1.1.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
},
"ip-regex": {
"version": "2.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
},
"is-callable": {
"version": "1.1.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
},
"is-ci": {
"version": "1.2.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
"requires": {
"ci-info": "^1.5.0"
},
"dependencies": {
"ci-info": {
"version": "1.6.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A=="
}
}
},
"is-cidr": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==",
"requires": {
"cidr-regex": "^2.0.10"
}
},
"is-date-object": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
},
"is-fullwidth-code-point": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"requires": {
"number-is-nan": "^1.0.0"
}
},
"is-installed-globally": {
"version": "0.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
"requires": {
"global-dirs": "^0.1.0",
"is-path-inside": "^1.0.0"
@@ -12474,85 +13830,103 @@
},
"is-npm": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ="
},
"is-obj": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
},
"is-path-inside": {
"version": "1.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
"requires": {
"path-is-inside": "^1.0.1"
}
},
"is-redirect": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
},
"is-regex": {
"version": "1.0.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
"requires": {
"has": "^1.0.1"
}
},
"is-retry-allowed": {
"version": "1.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="
},
"is-stream": {
"version": "1.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-symbol": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
"requires": {
"has-symbols": "^1.0.0"
}
},
"is-typedarray": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"isarray": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isexe": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"isstream": {
"version": "0.1.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"jsbn": {
"version": "0.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"optional": true
},
"json-parse-better-errors": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
},
"json-schema": {
"version": "0.4.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"json-stringify-safe": {
"version": "5.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsonparse": {
"version": "1.3.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
},
"jsprim": {
"version": "1.4.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -12562,18 +13936,21 @@
},
"latest-version": {
"version": "3.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
"requires": {
"package-json": "^4.0.0"
}
},
"lazy-property": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc="
},
"libcipm": {
"version": "4.0.8",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==",
"requires": {
"bin-links": "^1.1.2",
"bluebird": "^3.5.1",
@@ -12594,7 +13971,8 @@
},
"libnpm": {
"version": "3.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==",
"requires": {
"bin-links": "^1.1.2",
"bluebird": "^3.5.3",
@@ -12620,7 +13998,8 @@
},
"libnpmaccess": {
"version": "3.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==",
"requires": {
"aproba": "^2.0.0",
"get-stream": "^4.0.0",
@@ -12630,7 +14009,8 @@
},
"libnpmconfig": {
"version": "1.2.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==",
"requires": {
"figgy-pudding": "^3.5.1",
"find-up": "^3.0.0",
@@ -12639,14 +14019,16 @@
"dependencies": {
"find-up": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"requires": {
"locate-path": "^3.0.0"
}
},
"locate-path": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
@@ -12654,27 +14036,31 @@
},
"p-limit": {
"version": "2.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
}
}
},
"libnpmhook": {
"version": "5.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==",
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.4.1",
@@ -12684,7 +14070,8 @@
},
"libnpmorg": {
"version": "1.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==",
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.4.1",
@@ -12694,7 +14081,8 @@
},
"libnpmpublish": {
"version": "1.1.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==",
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.5.1",
@@ -12709,7 +14097,8 @@
},
"libnpmsearch": {
"version": "2.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==",
"requires": {
"figgy-pudding": "^3.5.1",
"get-stream": "^4.0.0",
@@ -12718,7 +14107,8 @@
},
"libnpmteam": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==",
"requires": {
"aproba": "^2.0.0",
"figgy-pudding": "^3.4.1",
@@ -12728,7 +14118,8 @@
},
"libnpx": {
"version": "10.2.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==",
"requires": {
"dotenv": "^5.0.1",
"npm-package-arg": "^6.0.0",
@@ -12742,7 +14133,8 @@
},
"lock-verify": {
"version": "2.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==",
"requires": {
"npm-package-arg": "^6.1.0",
"semver": "^5.4.1"
@@ -12750,18 +14142,21 @@
},
"lockfile": {
"version": "1.0.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==",
"requires": {
"signal-exit": "^3.0.2"
}
},
"lodash._baseindexof": {
"version": "3.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw="
},
"lodash._baseuniq": {
"version": "4.6.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
"requires": {
"lodash._createset": "~4.0.0",
"lodash._root": "~3.0.0"
@@ -12769,72 +14164,87 @@
},
"lodash._bindcallback": {
"version": "3.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4="
},
"lodash._cacheindexof": {
"version": "3.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI="
},
"lodash._createcache": {
"version": "3.1.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=",
"requires": {
"lodash._getnative": "^3.0.0"
}
},
"lodash._createset": {
"version": "4.0.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY="
},
"lodash._getnative": {
"version": "3.9.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U="
},
"lodash._root": {
"version": "3.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
},
"lodash.clonedeep": {
"version": "4.5.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
"lodash.restparam": {
"version": "3.6.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU="
},
"lodash.union": {
"version": "4.6.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
},
"lodash.uniq": {
"version": "4.5.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M="
},
"lodash.without": {
"version": "4.4.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw="
},
"lowercase-keys": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
},
"lru-cache": {
"version": "5.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": {
"yallist": "^3.0.2"
}
},
"make-dir": {
"version": "1.3.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
"requires": {
"pify": "^3.0.0"
}
},
"make-fetch-happen": {
"version": "5.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==",
"requires": {
"agentkeepalive": "^3.4.1",
"cacache": "^12.0.0",
@@ -12851,40 +14261,47 @@
},
"meant": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg=="
},
"mime-db": {
"version": "1.35.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
},
"mime-types": {
"version": "2.1.19",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
"requires": {
"mime-db": "~1.35.0"
}
},
"minimatch": {
"version": "3.0.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
- "version": "1.2.5",
- "bundled": true
+ "version": "1.2.6",
+ "resolved": false,
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"minizlib": {
"version": "1.3.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"requires": {
"minipass": "^2.9.0"
},
"dependencies": {
"minipass": {
"version": "2.9.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -12894,7 +14311,8 @@
},
"mississippi": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
"requires": {
"concat-stream": "^1.5.0",
"duplexify": "^3.4.2",
@@ -12910,20 +14328,23 @@
},
"mkdirp": {
"version": "0.5.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
},
"dependencies": {
"minimist": {
- "version": "1.2.5",
- "bundled": true
+ "version": "1.2.6",
+ "resolved": false,
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
}
}
},
"move-concurrently": {
"version": "1.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
"requires": {
"aproba": "^1.1.1",
"copy-concurrently": "^1.0.0",
@@ -12935,21 +14356,25 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
}
}
},
"ms": {
"version": "2.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"mute-stream": {
"version": "0.0.7",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
},
"node-fetch-npm": {
"version": "2.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
"requires": {
"encoding": "^0.1.11",
"json-parse-better-errors": "^1.0.0",
@@ -12958,7 +14383,8 @@
},
"node-gyp": {
"version": "5.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==",
"requires": {
"env-paths": "^2.2.0",
"glob": "^7.1.4",
@@ -12975,7 +14401,8 @@
},
"nopt": {
"version": "4.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
@@ -12983,7 +14410,8 @@
},
"normalize-package-data": {
"version": "2.5.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"requires": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
@@ -12993,7 +14421,8 @@
"dependencies": {
"resolve": {
"version": "1.10.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
"requires": {
"path-parse": "^1.0.6"
}
@@ -13002,7 +14431,8 @@
},
"npm-audit-report": {
"version": "1.3.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==",
"requires": {
"cli-table3": "^0.5.0",
"console-control-strings": "^1.1.0"
@@ -13010,25 +14440,29 @@
},
"npm-bundled": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
"requires": {
"npm-normalize-package-bin": "^1.0.1"
}
},
"npm-cache-filename": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE="
},
"npm-install-checks": {
"version": "3.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==",
"requires": {
"semver": "^2.3.0 || 3.x || 4 || 5"
}
},
"npm-lifecycle": {
"version": "3.1.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==",
"requires": {
"byline": "^5.0.0",
"graceful-fs": "^4.1.15",
@@ -13042,15 +14476,18 @@
},
"npm-logical-tree": {
"version": "1.2.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg=="
},
"npm-normalize-package-bin": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
},
"npm-package-arg": {
"version": "6.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==",
"requires": {
"hosted-git-info": "^2.7.1",
"osenv": "^0.1.5",
@@ -13060,7 +14497,8 @@
},
"npm-packlist": {
"version": "1.4.8",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
"requires": {
"ignore-walk": "^3.0.1",
"npm-bundled": "^1.0.1",
@@ -13069,7 +14507,8 @@
},
"npm-pick-manifest": {
"version": "3.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==",
"requires": {
"figgy-pudding": "^3.5.1",
"npm-package-arg": "^6.0.0",
@@ -13078,7 +14517,8 @@
},
"npm-profile": {
"version": "4.0.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==",
"requires": {
"aproba": "^1.1.2 || 2",
"figgy-pudding": "^3.4.1",
@@ -13087,7 +14527,8 @@
},
"npm-registry-fetch": {
"version": "4.0.7",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==",
"requires": {
"JSONStream": "^1.3.4",
"bluebird": "^3.5.1",
@@ -13100,24 +14541,28 @@
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
},
"npm-run-path": {
"version": "2.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
"requires": {
"path-key": "^2.0.0"
}
},
"npm-user-validate": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw=="
},
"npmlog": {
"version": "4.1.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
@@ -13127,23 +14572,28 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"oauth-sign": {
"version": "0.9.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-assign": {
"version": "4.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-keys": {
"version": "1.0.12",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag=="
},
"object.getownpropertydescriptors": {
"version": "2.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
"requires": {
"define-properties": "^1.1.2",
"es-abstract": "^1.5.1"
@@ -13151,26 +14601,31 @@
},
"once": {
"version": "1.4.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"opener": {
"version": "1.5.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
},
"os-homedir": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
},
"os-tmpdir": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"osenv": {
"version": "0.1.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"requires": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
@@ -13178,11 +14633,13 @@
},
"p-finally": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"package-json": {
"version": "4.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
"requires": {
"got": "^6.7.1",
"registry-auth-token": "^3.0.1",
@@ -13192,7 +14649,8 @@
},
"pacote": {
"version": "9.5.12",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==",
"requires": {
"bluebird": "^3.5.3",
"cacache": "^12.0.2",
@@ -13228,7 +14686,8 @@
"dependencies": {
"minipass": {
"version": "2.9.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -13238,7 +14697,8 @@
},
"parallel-transform": {
"version": "1.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
"requires": {
"cyclist": "~0.2.2",
"inherits": "^2.0.3",
@@ -13247,7 +14707,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -13260,7 +14721,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -13269,47 +14731,58 @@
},
"path-exists": {
"version": "3.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
},
"path-is-absolute": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-is-inside": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM="
},
"path-key": {
"version": "2.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
},
"path-parse": {
"version": "1.0.7",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"performance-now": {
"version": "2.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"pify": {
"version": "3.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
},
"prepend-http": {
"version": "1.0.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"process-nextick-args": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"promise-inflight": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"promise-retry": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
"requires": {
"err-code": "^1.0.0",
"retry": "^0.10.0"
@@ -13317,43 +14790,51 @@
"dependencies": {
"retry": {
"version": "0.10.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q="
}
}
},
"promzard": {
"version": "0.3.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=",
"requires": {
"read": "1"
}
},
"proto-list": {
"version": "1.2.4",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
},
"protoduck": {
"version": "5.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==",
"requires": {
"genfun": "^5.0.0"
}
},
"prr": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
},
"pseudomap": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"psl": {
"version": "1.1.29",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
},
"pump": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -13361,7 +14842,8 @@
},
"pumpify": {
"version": "1.5.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
"requires": {
"duplexify": "^3.6.0",
"inherits": "^2.0.3",
@@ -13370,7 +14852,8 @@
"dependencies": {
"pump": {
"version": "2.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -13380,19 +14863,23 @@
},
"punycode": {
"version": "1.4.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"qrcode-terminal": {
"version": "0.12.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="
},
"qs": {
"version": "6.5.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
},
"query-string": {
"version": "6.8.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==",
"requires": {
"decode-uri-component": "^0.2.0",
"split-on-first": "^1.0.0",
@@ -13401,11 +14888,13 @@
},
"qw": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ="
},
"rc": {
"version": "1.2.8",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -13415,21 +14904,24 @@
},
"read": {
"version": "1.0.7",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
"requires": {
"mute-stream": "~0.0.4"
}
},
"read-cmd-shim": {
"version": "1.0.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==",
"requires": {
"graceful-fs": "^4.1.2"
}
},
"read-installed": {
"version": "4.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=",
"requires": {
"debuglog": "^1.0.1",
"graceful-fs": "^4.1.2",
@@ -13442,7 +14934,8 @@
},
"read-package-json": {
"version": "2.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==",
"requires": {
"glob": "^7.1.1",
"graceful-fs": "^4.1.2",
@@ -13453,7 +14946,8 @@
},
"read-package-tree": {
"version": "5.3.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
"requires": {
"read-package-json": "^2.0.0",
"readdir-scoped-modules": "^1.0.0",
@@ -13462,7 +14956,8 @@
},
"readable-stream": {
"version": "3.6.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -13471,7 +14966,8 @@
},
"readdir-scoped-modules": {
"version": "1.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
"requires": {
"debuglog": "^1.0.1",
"dezalgo": "^1.0.0",
@@ -13481,7 +14977,8 @@
},
"registry-auth-token": {
"version": "3.4.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
"requires": {
"rc": "^1.1.6",
"safe-buffer": "^5.0.1"
@@ -13489,14 +14986,16 @@
},
"registry-url": {
"version": "3.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
"requires": {
"rc": "^1.0.1"
}
},
"request": {
"version": "2.88.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@@ -13522,96 +15021,115 @@
},
"require-directory": {
"version": "2.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"resolve-from": {
"version": "4.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
},
"retry": {
"version": "0.12.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
},
"rimraf": {
"version": "2.7.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"requires": {
"glob": "^7.1.3"
}
},
"run-queue": {
"version": "1.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
"requires": {
"aproba": "^1.1.1"
},
"dependencies": {
"aproba": {
"version": "1.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
}
}
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"semver": {
"version": "5.7.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"semver-diff": {
"version": "2.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
"requires": {
"semver": "^5.0.3"
}
},
"set-blocking": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"sha": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==",
"requires": {
"graceful-fs": "^4.1.2"
}
},
"shebang-command": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"requires": {
"shebang-regex": "^1.0.0"
}
},
"shebang-regex": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"signal-exit": {
"version": "3.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"slide": {
"version": "1.1.6",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
},
"smart-buffer": {
"version": "4.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw=="
},
"socks": {
"version": "2.3.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
"requires": {
"ip": "1.1.5",
"smart-buffer": "^4.1.0"
@@ -13619,7 +15137,8 @@
},
"socks-proxy-agent": {
"version": "4.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
"requires": {
"agent-base": "~4.2.1",
"socks": "~2.3.2"
@@ -13627,7 +15146,8 @@
"dependencies": {
"agent-base": {
"version": "4.2.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
"requires": {
"es6-promisify": "^5.0.0"
}
@@ -13636,11 +15156,13 @@
},
"sorted-object": {
"version": "2.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw="
},
"sorted-union-stream": {
"version": "2.1.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=",
"requires": {
"from2": "^1.3.0",
"stream-iterate": "^1.1.0"
@@ -13648,7 +15170,8 @@
"dependencies": {
"from2": {
"version": "1.3.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=",
"requires": {
"inherits": "~2.0.1",
"readable-stream": "~1.1.10"
@@ -13656,11 +15179,13 @@
},
"isarray": {
"version": "0.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
@@ -13670,13 +15195,15 @@
},
"string_decoder": {
"version": "0.10.31",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"spdx-correct": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
@@ -13684,11 +15211,13 @@
},
"spdx-exceptions": {
"version": "2.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg=="
},
"spdx-expression-parse": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
@@ -13696,15 +15225,18 @@
},
"spdx-license-ids": {
"version": "3.0.5",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
},
"split-on-first": {
"version": "1.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
},
"sshpk": {
"version": "1.14.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@@ -13719,14 +15251,16 @@
},
"ssri": {
"version": "6.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
"requires": {
"figgy-pudding": "^3.5.1"
}
},
"stream-each": {
"version": "1.2.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==",
"requires": {
"end-of-stream": "^1.1.0",
"stream-shift": "^1.0.0"
@@ -13734,7 +15268,8 @@
},
"stream-iterate": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=",
"requires": {
"readable-stream": "^2.1.5",
"stream-shift": "^1.0.0"
@@ -13742,7 +15277,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -13755,7 +15291,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -13764,15 +15301,18 @@
},
"stream-shift": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
},
"strict-uri-encode": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string-width": {
"version": "2.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
@@ -13780,15 +15320,18 @@
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"strip-ansi": {
"version": "4.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
@@ -13810,33 +15353,39 @@
},
"stringify-package": {
"version": "1.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg=="
},
"strip-ansi": {
"version": "3.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-eof": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"strip-json-comments": {
"version": "2.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"supports-color": {
"version": "5.4.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"requires": {
"has-flag": "^3.0.0"
}
},
"tar": {
"version": "4.4.19",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==",
"requires": {
"chownr": "^1.1.4",
"fs-minipass": "^1.2.7",
@@ -13849,7 +15398,8 @@
"dependencies": {
"minipass": {
"version": "2.9.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -13857,32 +15407,38 @@
},
"safe-buffer": {
"version": "5.2.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"yallist": {
"version": "3.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
}
},
"term-size": {
"version": "1.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
"requires": {
"execa": "^0.7.0"
}
},
"text-table": {
"version": "0.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
},
"through": {
"version": "2.3.8",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "2.0.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
"requires": {
"readable-stream": "^2.1.5",
"xtend": "~4.0.1"
@@ -13890,7 +15446,8 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -13903,7 +15460,8 @@
},
"string_decoder": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
@@ -13912,15 +15470,18 @@
},
"timed-out": {
"version": "4.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
},
"tiny-relative-date": {
"version": "1.3.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A=="
},
"tough-cookie": {
"version": "2.4.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
@@ -13928,60 +15489,71 @@
},
"tunnel-agent": {
"version": "0.6.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"tweetnacl": {
"version": "0.14.5",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
},
"typedarray": {
"version": "0.0.6",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"uid-number": {
"version": "0.0.6",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE="
},
"umask": {
"version": "1.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0="
},
"unique-filename": {
"version": "1.1.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
"requires": {
"unique-slug": "^2.0.0"
}
},
"unique-slug": {
"version": "2.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
"requires": {
"imurmurhash": "^0.1.4"
}
},
"unique-string": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
"requires": {
"crypto-random-string": "^1.0.0"
}
},
"unpipe": {
"version": "1.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"unzip-response": {
"version": "2.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c="
},
"update-notifier": {
"version": "2.5.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
"requires": {
"boxen": "^1.2.1",
"chalk": "^2.0.1",
@@ -13997,46 +15569,54 @@
},
"uri-js": {
"version": "4.4.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
"requires": {
"punycode": "^2.1.0"
},
"dependencies": {
"punycode": {
"version": "2.1.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
},
"url-parse-lax": {
"version": "1.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
"requires": {
"prepend-http": "^1.0.1"
}
},
"util-deprecate": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"util-extend": {
"version": "1.0.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8="
},
"util-promisify": {
"version": "2.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
"requires": {
"object.getownpropertydescriptors": "^2.0.3"
}
},
"uuid": {
"version": "3.3.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
},
"validate-npm-package-license": {
"version": "3.0.4",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"requires": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
@@ -14044,14 +15624,16 @@
},
"validate-npm-package-name": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
"requires": {
"builtins": "^1.0.3"
}
},
"verror": {
"version": "1.10.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
@@ -14060,32 +15642,37 @@
},
"wcwidth": {
"version": "1.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"requires": {
"defaults": "^1.0.3"
}
},
"which": {
"version": "1.3.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"requires": {
"isexe": "^2.0.0"
}
},
"which-module": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wide-align": {
"version": "1.1.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"requires": {
"string-width": "^1.0.2"
},
"dependencies": {
"string-width": {
"version": "1.0.2",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -14096,21 +15683,24 @@
},
"widest-line": {
"version": "2.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
"requires": {
"string-width": "^2.1.1"
}
},
"worker-farm": {
"version": "1.7.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
"requires": {
"errno": "~0.1.7"
}
},
"wrap-ansi": {
"version": "5.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
@@ -14118,16 +15708,19 @@
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
- "bundled": true
+ "version": "4.1.1",
+ "resolved": false,
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"string-width": {
"version": "3.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -14136,7 +15729,8 @@
},
"strip-ansi": {
"version": "5.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -14145,11 +15739,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"write-file-atomic": {
"version": "2.4.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
"requires": {
"graceful-fs": "^4.1.11",
"imurmurhash": "^0.1.4",
@@ -14158,23 +15754,28 @@
},
"xdg-basedir": {
"version": "3.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ="
},
"xtend": {
"version": "4.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
},
"y18n": {
"version": "4.0.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ=="
},
"yallist": {
"version": "3.0.3",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
},
"yargs": {
"version": "14.2.3",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
"requires": {
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
@@ -14191,22 +15792,26 @@
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"find-up": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"requires": {
"locate-path": "^3.0.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"locate-path": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
@@ -14214,25 +15819,29 @@
},
"p-limit": {
"version": "2.3.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"string-width": {
"version": "3.1.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@@ -14241,7 +15850,8 @@
},
"strip-ansi": {
"version": "5.2.0",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
@@ -14250,7 +15860,8 @@
},
"yargs-parser": {
"version": "15.0.1",
- "bundled": true,
+ "resolved": false,
+ "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
@@ -14258,7 +15869,8 @@
"dependencies": {
"camelcase": {
"version": "5.3.1",
- "bundled": true
+ "resolved": false,
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
}
}
}
@@ -14273,20 +15885,20 @@
}
},
"npmlog": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
+ "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"requires": {
- "are-we-there-yet": "~1.1.2",
- "console-control-strings": "~1.1.0",
- "gauge": "~2.7.3",
- "set-blocking": "~2.0.0"
+ "are-we-there-yet": "^2.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^3.0.0",
+ "set-blocking": "^2.0.0"
}
},
"nth-check": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
- "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"requires": {
"boolbase": "^1.0.0"
}
@@ -14351,9 +15963,9 @@
}
},
"object-inspect": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
- "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw=="
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+ "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
},
"object-is": {
"version": "1.1.5",
@@ -14381,6 +15993,11 @@
"unpack-string": "0.0.2"
},
"dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
"inspect-parameters-declaration": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/inspect-parameters-declaration/-/inspect-parameters-declaration-0.0.10.tgz",
@@ -14424,13 +16041,47 @@
"object-keys": "^1.0.11"
}
},
- "object.getownpropertydescriptors": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz",
- "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==",
+ "object.entries": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz",
+ "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.1"
+ }
+ },
+ "object.fromentries": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz",
+ "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==",
+ "dev": true,
"requires": {
+ "call-bind": "^1.0.2",
"define-properties": "^1.1.3",
- "es-abstract": "^1.17.0-next.1"
+ "es-abstract": "^1.19.1"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz",
+ "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==",
+ "requires": {
+ "array.prototype.reduce": "^1.0.4",
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.1"
+ }
+ },
+ "object.hasown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz",
+ "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.19.5"
}
},
"object.pick": {
@@ -14441,6 +16092,17 @@
"isobject": "^3.0.1"
}
},
+ "object.values": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz",
+ "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.1"
+ }
+ },
"obuf": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
@@ -14448,9 +16110,9 @@
"dev": true
},
"on-finished": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
- "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"requires": {
"ee-first": "1.1.1"
}
@@ -14472,6 +16134,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
@@ -14517,18 +16180,9 @@
}
},
"orderedmap": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-1.1.5.tgz",
- "integrity": "sha512-/fzlCGKRmfayGoI9UUXvJfc2nMZlJHW30QqEvwPvlg8tsX7jyiUSomYie6mYqx7Z9bOMGoag0H/q1PS/0PjYkg=="
- },
- "original": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
- "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
- "dev": true,
- "requires": {
- "url-parse": "^1.4.3"
- }
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.0.0.tgz",
+ "integrity": "sha512-buf4PoAMlh45b8a8gsGy/X6w279TSqkyAS0C0wdTSJwFSU+ljQFJON5I8NfjLHoCXwpSROIo2wr0g33T+kQshQ=="
},
"os-homedir": {
"version": "1.0.2",
@@ -14611,6 +16265,11 @@
"semver": "^5.1.0"
},
"dependencies": {
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ=="
+ },
"got": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
@@ -14676,13 +16335,6 @@
"requires": {
"dot-case": "^3.0.4",
"tslib": "^2.0.3"
- },
- "dependencies": {
- "tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
- }
}
},
"parent-module": {
@@ -14704,6 +16356,12 @@
"lines-and-columns": "^1.1.6"
}
},
+ "parse5": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
+ "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
+ "dev": true
+ },
"parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
@@ -14726,13 +16384,6 @@
"requires": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
- },
- "dependencies": {
- "tslib": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
- }
}
},
"pascalcase": {
@@ -14766,9 +16417,9 @@
}
},
"passport-oauth2": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz",
- "integrity": "sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==",
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.1.tgz",
+ "integrity": "sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ==",
"requires": {
"base64url": "3.x.x",
"oauth": "0.9.x",
@@ -14813,9 +16464,9 @@
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
},
"path-parse": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
- "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"path-to-regexp": {
"version": "0.1.7",
@@ -14872,11 +16523,12 @@
}
},
"pdfjs-dist": {
- "version": "2.13.216",
- "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.13.216.tgz",
- "integrity": "sha512-qn/9a/3IHIKZarTK6ajeeFXBkG15Lg1Fx99PxU09PAU2i874X8mTcHJYyDJxu7WDfNhV6hM7bRQBZU384anoqQ==",
+ "version": "2.14.305",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.14.305.tgz",
+ "integrity": "sha512-5f7i25J1dKIBczhgfxEgNxfYNIxXEdxqo6Qb4ehY7Ja+p6AI4uUmk/OcVGXfRGm2ys5iaJJhJUwBFwv6Jl/Qww==",
"requires": {
- "web-streams-polyfill": "^3.2.0"
+ "dommatrix": "^1.0.1",
+ "web-streams-polyfill": "^3.2.1"
}
},
"pend": {
@@ -14884,6 +16536,11 @@
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
},
+ "perfect-scrollbar": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz",
+ "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g=="
+ },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -14897,14 +16554,12 @@
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
- "dev": true
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
},
"pinkie": {
"version": "2.0.4",
@@ -14925,56 +16580,42 @@
"integrity": "sha512-6Rtbp7criZRwedlvWbUYxqlqJoAlMvYHo2UcRWq79xZ54vZcaNHpVBOcWkX3ErT2aUA69tv+uiv4zKJbhD/Wgg=="
},
"pkg-dir": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
- "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
- "dev": true,
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"requires": {
- "find-up": "^2.1.0"
+ "find-up": "^4.0.0"
},
"dependencies": {
"find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
- "dev": true,
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
- "locate-path": "^2.0.0"
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
}
},
"locate-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
- "dev": true,
- "requires": {
- "p-locate": "^2.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "p-limit": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
- "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
- "dev": true,
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
- "p-try": "^1.0.0"
+ "p-locate": "^4.1.0"
}
},
"p-locate": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
- "dev": true,
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
- "p-limit": "^1.1.0"
+ "p-limit": "^2.2.0"
}
},
- "p-try": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
- "dev": true
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
}
}
},
@@ -15027,12 +16668,12 @@
},
"dependencies": {
"mkdirp": {
- "version": "0.5.5",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
- "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"requires": {
- "minimist": "^1.2.5"
+ "minimist": "^1.2.6"
}
}
}
@@ -15043,30 +16684,26 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
"postcss": {
- "version": "7.0.27",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz",
- "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==",
+ "version": "7.0.39",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
+ "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"dev": true,
"requires": {
- "chalk": "^2.4.2",
- "source-map": "^0.6.1",
- "supports-color": "^6.1.0"
+ "picocolors": "^0.2.1",
+ "source-map": "^0.6.1"
},
"dependencies": {
+ "picocolors": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
+ "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==",
+ "dev": true
+ },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
- },
- "supports-color": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
- "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
- "dev": true,
- "requires": {
- "has-flag": "^3.0.0"
- }
}
}
},
@@ -15111,14 +16748,13 @@
}
},
"postcss-selector-parser": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
- "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
- "indexes-of": "^1.0.1",
- "uniq": "^1.0.1"
+ "util-deprecate": "^1.0.2"
}
},
"postcss-value-parser": {
@@ -15127,15 +16763,15 @@
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
},
"prebuild-install": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
- "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
+ "version": "5.3.6",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz",
+ "integrity": "sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
- "minimist": "^1.2.0",
- "mkdirp": "^0.5.1",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
@@ -15146,6 +16782,98 @@
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ },
+ "are-we-there-yet": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
+ "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ }
}
},
"prelude-ls": {
@@ -15159,6 +16887,21 @@
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
+ "prettier": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
+ "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+ "dev": true
+ },
+ "prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "requires": {
+ "fast-diff": "^1.1.2"
+ }
+ },
"pretty-error": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
@@ -15218,19 +16961,19 @@
"dev": true
},
"prop-types": {
- "version": "15.7.2",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
- "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
- "react-is": "^16.8.1"
+ "react-is": "^16.13.1"
}
},
"prosemirror-commands": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.2.tgz",
- "integrity": "sha512-TX+KpWudMon06frryfpO/u7hsQv2hu8L4VSVbCpi3/7wXHBgl+35mV85qfa3RpT8xD2f3MdeoTqH0vy5JdbXPg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.3.0.tgz",
+ "integrity": "sha512-BwBbZ5OAScPcm0x7H8SPbqjuEJnCU2RJT9LDyOiiIl/3NbL1nJZI4SFNHwU2e/tRr2Xe7JsptpzseqvZvToLBQ==",
"requires": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
@@ -15263,9 +17006,9 @@
"integrity": "sha1-QgsENNF5xdBJD44hSNhVGpVJY4I="
},
"prosemirror-history": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.2.0.tgz",
- "integrity": "sha512-B9v9xtf4fYbKxQwIr+3wtTDNLDZcmMMmGiI3TAPShnUzvo+Rmv1GiUrsQChY1meetHl7rhML2cppF3FTs7f7UQ==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.0.tgz",
+ "integrity": "sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA==",
"requires": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
@@ -15273,61 +17016,62 @@
}
},
"prosemirror-inputrules": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.1.3.tgz",
- "integrity": "sha512-ZaHCLyBtvbyIHv0f5p6boQTIJjlD6o2NPZiEaZWT2DA+j591zS29QQEMT4lBqwcLW3qRSf7ZvoKNbf05YrsStw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.2.0.tgz",
+ "integrity": "sha512-eAW/M/NTSSzpCOxfR8Abw6OagdG0MiDAiWHQMQveIsZtoKVYzm0AflSPq/ymqJd56/Su1YPbwy9lM13wgHOFmQ==",
"requires": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"prosemirror-keymap": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.1.5.tgz",
- "integrity": "sha512-8SZgPH3K+GLsHL2wKuwBD9rxhsbnVBTwpHCO4VUO5GmqUQlxd/2GtBVWTsyLq4Dp3N9nGgPd3+lZFKUDuVp+Vw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.0.tgz",
+ "integrity": "sha512-TdSfu+YyLDd54ufN/ZeD1VtBRYpgZnTPnnbY+4R08DDgs84KrIPEPbJL8t1Lm2dkljFx6xeBE26YWH3aIzkPKg==",
"requires": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"prosemirror-model": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.16.1.tgz",
- "integrity": "sha512-r1/w0HDU40TtkXp0DyKBnFPYwd8FSlUSJmGCGFv4DeynfeSlyQF2FD0RQbVEMOe6P3PpUSXM6LZBV7W/YNZ4mA==",
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.18.1.tgz",
+ "integrity": "sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==",
"requires": {
- "orderedmap": "^1.1.0"
+ "orderedmap": "^2.0.0"
}
},
"prosemirror-schema-list": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.1.6.tgz",
- "integrity": "sha512-aFGEdaCWmJzouZ8DwedmvSsL50JpRkqhQ6tcpThwJONVVmCgI36LJHtoQ4VGZbusMavaBhXXr33zyD2IVsTlkw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.2.0.tgz",
+ "integrity": "sha512-8PT/9xOx1HHdC7fDNNfhQ50Z8Mzu7nKyA1KCDltSpcZVZIbB0k7KtsHrnXyuIhbLlScoymBiLZ00c5MH6wdFsA==",
"requires": {
"prosemirror-model": "^1.0.0",
+ "prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"prosemirror-state": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.3.4.tgz",
- "integrity": "sha512-Xkkrpd1y/TQ6HKzN3agsQIGRcLckUMA9u3j207L04mt8ToRgpGeyhbVv0HI7omDORIBHjR29b7AwlATFFf2GLA==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.1.tgz",
+ "integrity": "sha512-U/LBDW2gNmVa07sz/D229XigSdDQ5CLFwVB1Vb32MJbAHHhWe/6pOc721faI17tqw4pZ49i1xfY/jEZ9tbIhPg==",
"requires": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"prosemirror-transform": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.3.4.tgz",
- "integrity": "sha512-gTsg3UIeaFuEY6+YmNPMgTpEkCKPedkFIUnsPpOMbclU701fEVI/e4VOXACXh3BO5rZJaBbEBwrnzB0mLp6eBA==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.6.0.tgz",
+ "integrity": "sha512-MAp7AjsjEGEqQY0sSMufNIUuEyB1ZR9Fqlm8dTwwWwpEJRv/plsKjWXBbx52q3Ml8MtaMcd7ic14zAHVB3WaMw==",
"requires": {
"prosemirror-model": "^1.0.0"
}
},
"prosemirror-view": {
- "version": "1.23.9",
- "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.23.9.tgz",
- "integrity": "sha512-4ft8PuzYPLqQlYjkW7+jy3drquLrzB0cXu6bXyEgBEYnbv/uI/zBF1rGxmSZCDKwteKm1Wn24B+BSheSw/9sOA==",
+ "version": "1.26.5",
+ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.26.5.tgz",
+ "integrity": "sha512-SO+AX6WwdbJZHVvuloXI0qfO+YJAnZAat8qrYwfiqTQwL/FewLUnr0m3EXZ6a60hQs8/Q/lzeJXiFR/dOPaaKQ==",
"requires": {
"prosemirror-model": "^1.16.0",
"prosemirror-state": "^1.0.0",
@@ -15365,9 +17109,9 @@
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"pstree.remy": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz",
- "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A=="
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
},
"pug": {
"version": "2.0.4",
@@ -15395,9 +17139,9 @@
}
},
"pug-code-gen": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz",
- "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.3.tgz",
+ "integrity": "sha512-r9sezXdDuZJfW9J91TN/2LFbiqDhmltTFmGpHTsGdrNGp3p4SxAjjXEfnuK2e4ywYsRIVP0NeLbSAMHUcaX1EA==",
"requires": {
"constantinople": "^3.1.2",
"doctypes": "^1.1.0",
@@ -15426,6 +17170,21 @@
"pug-walk": "^1.1.8",
"resolve": "^1.1.6",
"uglify-js": "^2.6.1"
+ },
+ "dependencies": {
+ "clean-css": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
+ "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
+ "requires": {
+ "source-map": "~0.6.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ }
}
},
"pug-lexer": {
@@ -15543,11 +17302,11 @@
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g=="
},
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
- "ms": "^2.1.1"
+ "ms": "2.1.2"
}
},
"https-proxy-agent": {
@@ -15560,9 +17319,14 @@
}
},
"mime": {
- "version": "2.4.6",
- "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
- "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
@@ -15577,9 +17341,9 @@
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
"qs": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
},
"query-string": {
"version": "6.14.1",
@@ -15634,14 +17398,24 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
- "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+ "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"requires": {
"bytes": "3.1.2",
- "http-errors": "1.8.1",
+ "http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ }
}
},
"raw-loader": {
@@ -15651,18 +17425,6 @@
"requires": {
"loader-utils": "^1.1.0",
"schema-utils": "^1.0.0"
- },
- "dependencies": {
- "schema-utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
- "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
- "requires": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- }
- }
}
},
"rc": {
@@ -15687,13 +17449,11 @@
}
},
"react": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
- "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
+ "loose-envify": "^1.1.0"
}
},
"react-audio-waveform": {
@@ -15769,6 +17529,13 @@
"d3-array": "^1.2.4",
"prop-types": "^15.7.2",
"warning": "^3.0.0"
+ },
+ "dependencies": {
+ "d3-array": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+ "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+ }
}
},
"react-datepicker": {
@@ -15793,34 +17560,31 @@
}
},
"react-dom": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
- "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"requires": {
"loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
+ "scheduler": "^0.23.0"
},
"dependencies": {
"scheduler": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
- "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
+ "loose-envify": "^1.1.0"
}
}
}
},
"react-draggable": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.3.tgz",
- "integrity": "sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==",
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz",
+ "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==",
"requires": {
- "classnames": "^2.2.5",
- "prop-types": "^15.6.0"
+ "clsx": "^1.1.1",
+ "prop-types": "^15.8.1"
}
},
"react-event-listener": {
@@ -15844,26 +17608,32 @@
}
},
"react-grid-layout": {
- "version": "0.18.3",
- "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz",
- "integrity": "sha512-lHkrk941Tk5nTwZPa9uj6ttHBT0VehSHwEhWbINBJKvM1GRaFNOefvjcuxSyuCI5JWjVUP+Qm3ARt2470AlxMA==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz",
+ "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==",
"requires": {
- "classnames": "2.x",
+ "clsx": "^1.1.1",
"lodash.isequal": "^4.0.0",
- "prop-types": "^15.0.0",
+ "prop-types": "^15.8.1",
"react-draggable": "^4.0.0",
- "react-resizable": "^1.9.0"
+ "react-resizable": "^3.0.4"
+ },
+ "dependencies": {
+ "react-resizable": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz",
+ "integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==",
+ "requires": {
+ "prop-types": "15.x",
+ "react-draggable": "^4.0.3"
+ }
+ }
}
},
- "react-image-lightbox-with-rotate": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/react-image-lightbox-with-rotate/-/react-image-lightbox-with-rotate-5.1.1.tgz",
- "integrity": "sha512-5ZubUQefKSDGIiAwK4lkfmGr/bgIfNDHXqC+Fm6nbNwTVYuYOZ1RJjULOniEB4fxb3Vm0z/x0oNhi1lbP1aMtg==",
- "requires": {
- "blueimp-load-image": "^2.19.0",
- "prop-types": "^15.6.1",
- "react-modal": "^3.4.4"
- }
+ "react-icons": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz",
+ "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg=="
},
"react-input-autosize": {
"version": "3.0.0",
@@ -15903,9 +17673,9 @@
},
"dependencies": {
"@types/react": {
- "version": "17.0.40",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.40.tgz",
- "integrity": "sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==",
+ "version": "17.0.45",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.45.tgz",
+ "integrity": "sha512-YfhQ22Lah2e3CHPsb93tRwIGNiSwkuz1/blk4e6QrWS0jQzCSNbGLtOEYhPg02W0yGTTmpajp7dCTbBAMN3qsg==",
"optional": true,
"requires": {
"@types/prop-types": "*",
@@ -15914,28 +17684,23 @@
}
},
"@types/react-dom": {
- "version": "17.0.13",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.13.tgz",
- "integrity": "sha512-wEP+B8hzvy6ORDv1QBhcQia4j6ea4SFIBttHYpXKPFZRviBvknq0FRh3VrIxeXUmsPkwuXVZrVGG7KUVONmXCQ==",
+ "version": "17.0.17",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz",
+ "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==",
"optional": true,
"requires": {
- "@types/react": "*"
+ "@types/react": "^17"
}
},
- "acorn": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
- "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
- },
"core-js": {
- "version": "3.21.1",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz",
- "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig=="
+ "version": "3.22.8",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.8.tgz",
+ "integrity": "sha512-UoGQ/cfzGYIuiq6Z7vWL1HfkE9U9IZ4Ub+0XSiJTCzvbZzgPA69oDF2f+lgJ6dFFLEdjW5O6svvoKzXX23xFkA=="
},
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==",
"optional": true
}
}
@@ -15966,31 +17731,10 @@
"resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
"integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ=="
},
- "react-modal": {
- "version": "3.11.2",
- "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
- "integrity": "sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==",
- "requires": {
- "exenv": "^1.2.0",
- "prop-types": "^15.5.10",
- "react-lifecycles-compat": "^3.0.0",
- "warning": "^4.0.3"
- },
- "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-onclickoutside": {
- "version": "6.12.1",
- "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz",
- "integrity": "sha512-a5Q7CkWznBRUWPmocCvE8b6lEYw1s6+opp/60dCunhO+G6E4tDTO2Sd2jKE+leEnnrLAE2Wj5DlDHNqj5wPv1Q=="
+ "version": "6.12.2",
+ "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz",
+ "integrity": "sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA=="
},
"react-popper": {
"version": "1.3.11",
@@ -16032,9 +17776,9 @@
}
},
"react-redux": {
- "version": "7.2.6",
- "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
- "integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
+ "version": "7.2.8",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
+ "integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==",
"requires": {
"@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20",
@@ -16044,14 +17788,6 @@
"react-is": "^17.0.2"
},
"dependencies": {
- "@babel/runtime": {
- "version": "7.17.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz",
- "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==",
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- },
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -16060,9 +17796,9 @@
}
},
"react-refresh-typescript": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.3.tgz",
- "integrity": "sha512-CnR+UkpCFUIxW3u+KPxc6UhSO3M3aQxd9XCvgdry4a22LFwwITnjbANFUKtPGxA/Skw3PbZJrLiZ08UQnpXprQ=="
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.7.tgz",
+ "integrity": "sha512-KbuW57FauO11e6a9gU836sCm3M3z0b2+J2qPhUudg0QplOfz0eAF3gMeshcUC/ChfNLJCK1SZxvYmUtRxiZE5A=="
},
"react-resizable": {
"version": "1.11.1",
@@ -16112,9 +17848,9 @@
}
},
"csstype": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
},
"dom-helpers": {
"version": "5.2.1",
@@ -16139,9 +17875,24 @@
}
},
"react-table": {
- "version": "7.7.0",
- "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz",
- "integrity": "sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA=="
+ "version": "6.11.5",
+ "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz",
+ "integrity": "sha512-LM+AS9v//7Y7lAlgTWW/cW6Sn5VOb3EsSkKQfQTzOW8FngB1FUskLLNEVkAYsTX9LjOWR3QlGjykJqCE6eXT/g==",
+ "requires": {
+ "@types/react-table": "^6.8.5",
+ "classnames": "^2.2.5",
+ "react-is": "^16.8.1"
+ },
+ "dependencies": {
+ "@types/react-table": {
+ "version": "6.8.9",
+ "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.9.tgz",
+ "integrity": "sha512-fVQXjy/EYDbgraScgjDONA291McKqGrw0R0NeK639fx2bS4T19TnXMjg3FjOPlkI3qYTQtFTPADlRYysaQIMpA==",
+ "requires": {
+ "@types/react": "*"
+ }
+ }
+ }
},
"react-themeable": {
"version": "1.1.0",
@@ -16164,14 +17915,30 @@
"integrity": "sha512-EblIqTAsIpkYeM8bZtC4lcpTE0A2zCEGipFB52RgcQq/q+0oryrk7Sxt+sqhIjUu6xMNEVywV8dr74lz5yWO6A=="
},
"react-transition-group": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
- "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
+ "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"requires": {
- "dom-helpers": "^3.4.0",
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
- "prop-types": "^15.6.2",
- "react-lifecycles-compat": "^3.0.4"
+ "prop-types": "^15.6.2"
+ },
+ "dependencies": {
+ "csstype": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
+ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
+ },
+ "dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ }
}
},
"react-use-measure": {
@@ -16209,11 +17976,6 @@
"pify": "^2.0.0",
"pinkie-promise": "^2.0.0"
}
- },
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
}
}
},
@@ -16327,17 +18089,17 @@
"integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc="
},
"redux": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
- "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+ "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"regenerator-runtime": {
- "version": "0.13.5",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
- "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
+ "version": "0.13.9",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"regex-not": {
"version": "1.0.2",
@@ -16354,14 +18116,21 @@
"integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
},
"regexp.prototype.flags": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz",
- "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==",
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+ "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.3"
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
}
},
+ "regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true
+ },
"registry-auth-token": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
@@ -16407,9 +18176,9 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"dom-serializer": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
- "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
@@ -16417,14 +18186,14 @@
}
},
"domelementtype": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
- "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
},
"domhandler": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz",
- "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
"requires": {
"domelementtype": "^2.2.0"
}
@@ -16466,9 +18235,9 @@
}
},
"repeat-element": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
- "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g=="
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
+ "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ=="
},
"repeat-string": {
"version": "1.6.1",
@@ -16510,16 +18279,6 @@
"uuid": "^3.3.2"
},
"dependencies": {
- "form-data": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
- "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.6",
- "mime-types": "^2.1.12"
- }
- },
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
@@ -16542,14 +18301,6 @@
"tough-cookie": "^2.3.3"
},
"dependencies": {
- "request-promise-core": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
- "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
- "requires": {
- "lodash": "^4.17.19"
- }
- },
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
@@ -16562,21 +18313,20 @@
}
},
"request-promise-core": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
- "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
- "dev": true,
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
+ "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
"requires": {
- "lodash": "^4.17.15"
+ "lodash": "^4.17.19"
}
},
"request-promise-native": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz",
- "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
+ "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
"dev": true,
"requires": {
- "request-promise-core": "1.1.3",
+ "request-promise-core": "1.1.4",
"stealthy-require": "^1.1.1",
"tough-cookie": "^2.3.3"
},
@@ -16646,11 +18396,13 @@
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
- "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "version": "1.22.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
+ "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
"requires": {
- "path-parse": "^1.0.6"
+ "is-core-module": "^2.8.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
}
},
"resolve-alpn": {
@@ -16698,6 +18450,16 @@
}
}
},
+ "restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "requires": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@@ -16710,9 +18472,9 @@
"dev": true
},
"reveal.js": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.3.0.tgz",
- "integrity": "sha512-KzZxJjj1gmxVNyplY6g9MiGwtDvZJiYkMvG1Qmaita7vWT/8eoTEK+RuIPLvxDeyxOtTz56u2wrOETVO79qL4A=="
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.3.1.tgz",
+ "integrity": "sha512-1kyEnWeUkaCdBdX//XXq9dtBK95ppvIlSwlHelrP8/wrX6LcsYp4HT9WTFoFEOUBfVqkm8C2aHQ367o+UKfcxw=="
},
"right-align": {
"version": "0.1.3",
@@ -16731,9 +18493,9 @@
}
},
"rope-sequence": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.2.tgz",
- "integrity": "sha512-ku6MFrwEVSVmXLvy3dYph3LAMNS0890K7fabn+0YIRQ2T96T9F4gkFf0vf0WW0JUraNWwGRtInEpH7yO4tbQZg=="
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.3.tgz",
+ "integrity": "sha512-85aZYCxweiD5J8yTEbw+E6A27zSnLPNDL0WfPdw3YYodq7WjnTKo0q4dtyQ2gz23iPT8Q9CUyJtAaUNcTxRf5Q=="
},
"rtcpeerconnection-shim": {
"version": "1.2.15",
@@ -16743,6 +18505,12 @@
"sdp": "^2.6.0"
}
},
+ "run-async": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+ "dev": true
+ },
"run-queue": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
@@ -16750,6 +18518,31 @@
"dev": true,
"requires": {
"aproba": "^1.1.1"
+ },
+ "dependencies": {
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ }
+ }
+ },
+ "rxjs": {
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ }
}
},
"safe-buffer": {
@@ -16803,6 +18596,12 @@
"semver": "^6.3.0"
},
"dependencies": {
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ },
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -16830,34 +18629,13 @@
}
},
"schema-utils": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
- "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
- "dev": true,
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"requires": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "dependencies": {
- "ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "ajv-keywords": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "dev": true
- }
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
}
},
"scss-loader": {
@@ -16895,6 +18673,11 @@
"resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz",
"integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio="
},
+ "select": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+ "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
+ },
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -16908,14 +18691,6 @@
"dev": true,
"requires": {
"node-forge": "^0.10.0"
- },
- "dependencies": {
- "node-forge": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
- "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
- "dev": true
- }
}
},
"semver": {
@@ -16937,23 +18712,23 @@
}
},
"send": {
- "version": "0.17.2",
- "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
- "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"requires": {
"debug": "2.6.9",
- "depd": "~1.1.2",
- "destroy": "~1.0.4",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
- "http-errors": "1.8.1",
+ "http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
- "on-finished": "~2.3.0",
+ "on-finished": "2.4.1",
"range-parser": "~1.2.1",
- "statuses": "~1.5.0"
+ "statuses": "2.0.1"
},
"dependencies": {
"debug": {
@@ -16967,7 +18742,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}
}
},
@@ -16979,10 +18754,13 @@
}
},
"serialize-javascript": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
- "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
- "dev": true
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
},
"serializr": {
"version": "1.5.4",
@@ -17013,6 +18791,12 @@
"ms": "2.0.0"
}
},
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "dev": true
+ },
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@@ -17042,18 +18826,24 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
"dev": true
+ },
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "dev": true
}
}
},
"serve-static": {
- "version": "1.14.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
- "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
- "send": "0.17.2"
+ "send": "0.18.0"
}
},
"set-blocking": {
@@ -17061,11 +18851,6 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
- "set-immediate-shim": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
- "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
- },
"set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@@ -17084,11 +18869,6 @@
"requires": {
"is-extendable": "^0.1.0"
}
- },
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
}
}
},
@@ -17131,29 +18911,89 @@
"tunnel-agent": "^0.6.0"
},
"dependencies": {
- "fs-minipass": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
- "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ },
+ "are-we-there-yet": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
+ "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
"requires": {
- "minipass": "^3.0.0"
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
}
},
- "minipass": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz",
- "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==",
+ "chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
"requires": {
- "yallist": "^4.0.0"
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
}
},
- "minizlib": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz",
- "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==",
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
"requires": {
- "minipass": "^3.0.0",
- "yallist": "^4.0.0"
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "requires": {
+ "minimist": "^1.2.6"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
}
},
"semver": {
@@ -17161,23 +19001,36 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
},
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
"tar": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.5.tgz",
- "integrity": "sha512-MNIgJddrV2TkuwChwcSNds/5E9VijOiw7kAc1y5hTNJoLDSuIyid2QtLYiCYNnICebpuvjhPQZsXwUL0O3l7OQ==",
- "requires": {
- "chownr": "^1.1.3",
- "fs-minipass": "^2.0.0",
- "minipass": "^3.0.0",
- "minizlib": "^2.1.0",
- "mkdirp": "^0.5.0",
+ "version": "5.0.11",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.11.tgz",
+ "integrity": "sha512-E6q48d5y4XSCD+Xmwc0yc8lXuyDK38E0FB8N4S/drQRtXOMUhfhDxbB0xr2KKDhNfO51CFmoa6Oz00nAkWsjnA==",
+ "requires": {
+ "chownr": "^1.1.4",
+ "fs-minipass": "^2.1.0",
+ "minipass": "^3.1.3",
+ "minizlib": "^2.1.2",
+ "mkdirp": "^0.5.5",
"yallist": "^4.0.0"
}
- },
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
@@ -17212,13 +19065,6 @@
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
- },
- "dependencies": {
- "object-inspect": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
- "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
- }
}
},
"sift": {
@@ -17227,9 +19073,9 @@
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
},
"signal-exit": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
- "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"simple-assign": {
"version": "0.1.0",
@@ -17237,14 +19083,14 @@
"integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o="
},
"simple-concat": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
- "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
},
"simple-get": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
- "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
+ "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"requires": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
@@ -17266,17 +19112,23 @@
}
}
},
- "skmeans": {
- "version": "0.9.7",
- "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz",
- "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg=="
- },
"slash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true
},
+ "slice-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "astral-regex": "^1.0.0",
+ "is-fullwidth-code-point": "^2.0.0"
+ }
+ },
"sliced": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
@@ -17321,11 +19173,6 @@
"is-extendable": "^0.1.0"
}
},
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
- },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -17403,15 +19250,15 @@
}
},
"socket.io": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
- "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz",
+ "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==",
"requires": {
"debug": "~4.1.0",
- "engine.io": "~3.5.0",
+ "engine.io": "~3.6.0",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
- "socket.io-client": "2.4.0",
+ "socket.io-client": "2.5.0",
"socket.io-parser": "~3.4.0"
},
"dependencies": {
@@ -17431,9 +19278,9 @@
"integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
},
"socket.io-client": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
- "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz",
+ "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==",
"requires": {
"backo2": "1.0.2",
"component-bind": "1.0.0",
@@ -17459,12 +19306,12 @@
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
- "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+ "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"socket.io-parser": {
"version": "3.3.2",
@@ -17528,13 +19375,13 @@
}
},
"sockjs-client": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.0.tgz",
- "integrity": "sha512-qVHJlyfdHFht3eBFZdKEXKTlb7I4IV41xnVNo8yUKA1UHcPJwgW2SvTq9LhnjjCywSkSK7c/e4nghU0GOoMCRQ==",
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz",
+ "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==",
"dev": true,
"requires": {
"debug": "^3.2.7",
- "eventsource": "^1.1.0",
+ "eventsource": "^2.0.2",
"faye-websocket": "^0.11.4",
"inherits": "^2.0.4",
"url-parse": "^1.5.10"
@@ -17562,9 +19409,12 @@
},
"dependencies": {
"node-fetch": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
- "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "requires": {
+ "whatwg-url": "^5.0.0"
+ }
}
}
},
@@ -17586,10 +19436,9 @@
}
},
"source-map-support": {
- "version": "0.5.19",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
- "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
- "dev": true,
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -17598,15 +19447,14 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"source-map-url": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
- "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
+ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
},
"sparse-bitfield": {
"version": "3.0.3",
@@ -17618,9 +19466,9 @@
}
},
"spdx-correct": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
- "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+ "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
@@ -17632,18 +19480,18 @@
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
},
"spdx-expression-parse": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
- "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-license-ids": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
- "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz",
+ "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g=="
},
"spdy": {
"version": "4.0.2",
@@ -17730,9 +19578,9 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sshpk": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
- "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
+ "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@@ -17787,9 +19635,9 @@
}
},
"statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
},
"stdout-stream": {
"version": "1.4.1",
@@ -17887,42 +19735,40 @@
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
},
- "string.prototype.trimend": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
- "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
- }
- },
- "string.prototype.trimleft": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
- "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
+ "string.prototype.matchall": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz",
+ "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==",
+ "dev": true,
"requires": {
+ "call-bind": "^1.0.2",
"define-properties": "^1.1.3",
- "es-abstract": "^1.17.5",
- "string.prototype.trimstart": "^1.0.0"
+ "es-abstract": "^1.19.1",
+ "get-intrinsic": "^1.1.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.4.1",
+ "side-channel": "^1.0.4"
}
},
- "string.prototype.trimright": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
- "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
+ "string.prototype.trimend": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
+ "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
"requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5",
- "string.prototype.trimend": "^1.0.0"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.19.5"
}
},
"string.prototype.trimstart": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
- "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
+ "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
"requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.19.5"
}
},
"string_decoder": {
@@ -17942,6 +19788,11 @@
"unpack-string": "0.0.2"
},
"dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
"magicli": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
@@ -17976,11 +19827,6 @@
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
- "strip-final-newline": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
- },
"strip-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
@@ -18009,19 +19855,6 @@
"requires": {
"loader-utils": "^1.1.0",
"schema-utils": "^1.0.0"
- },
- "dependencies": {
- "schema-utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
- "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
- "dev": true,
- "requires": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- }
- }
}
},
"styled-components": {
@@ -18042,12 +19875,27 @@
"stylis": "^3.5.0",
"stylis-rule-sheet": "^0.0.10",
"supports-color": "^5.5.0"
+ },
+ "dependencies": {
+ "@emotion/is-prop-valid": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+ "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+ "requires": {
+ "@emotion/memoize": "0.7.4"
+ }
+ },
+ "stylis": {
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
+ "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q=="
+ }
}
},
"stylis": {
- "version": "3.5.4",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
- "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q=="
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
+ "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
},
"stylis-rule-sheet": {
"version": "0.0.10",
@@ -18055,9 +19903,9 @@
"integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw=="
},
"supercluster": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.4.tgz",
- "integrity": "sha512-GhKkRM1jMR6WUwGPw05fs66pOFWhf59lXq+Q3J3SxPvhNcmgOtLRV6aVQPMRsmXdpaeFJGivt+t7QXUPL3ff4g==",
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz",
+ "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==",
"requires": {
"kdbush": "^3.0.0"
}
@@ -18070,6 +19918,11 @@
"has-flag": "^3.0.0"
}
},
+ "supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
+ },
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
@@ -18081,6 +19934,46 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
+ "table": {
+ "version": "5.4.6",
+ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.10.2",
+ "lodash": "^4.17.14",
+ "slice-ansi": "^2.1.0",
+ "string-width": "^3.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
"table-layout": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz",
@@ -18094,10 +19987,9 @@
}
},
"tapable": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
- "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
- "dev": true
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
},
"tar": {
"version": "6.1.11",
@@ -18112,40 +20004,37 @@
"yallist": "^4.0.0"
},
"dependencies": {
- "chownr": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
- "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
- },
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
- },
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
"tar-fs": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
- "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+ "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
- "tar-stream": "^2.0.0"
+ "tar-stream": "^2.1.4"
+ },
+ "dependencies": {
+ "chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+ }
}
},
"tar-stream": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
- "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"requires": {
- "bl": "^4.0.1",
+ "bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
@@ -18175,79 +20064,55 @@
}
},
"terser": {
- "version": "5.12.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
- "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
- "dev": true,
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz",
+ "integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==",
"requires": {
+ "@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
- "source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
- "acorn": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
- "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
- "dev": true
- },
- "source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
- "dev": true
- },
- "source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
- "requires": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}
}
},
"terser-webpack-plugin": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz",
- "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==",
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz",
+ "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==",
"dev": true,
"requires": {
+ "@jridgewell/trace-mapping": "^0.3.7",
"jest-worker": "^27.4.5",
"schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.0",
- "source-map": "^0.6.1",
"terser": "^5.7.2"
},
"dependencies": {
- "serialize-javascript": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
- "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"requires": {
- "randombytes": "^2.1.0"
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
}
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
}
}
},
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
"textarea-caret": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
@@ -18301,6 +20166,11 @@
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
"integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
},
+ "tiny-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+ },
"tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
@@ -18321,10 +20191,19 @@
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA=="
},
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
- "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
+ "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
},
"to-fast-properties": {
"version": "2.0.0",
@@ -18413,13 +20292,9 @@
}
},
"tr46": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
- "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
- "dev": true,
- "requires": {
- "punycode": "^2.1.0"
- }
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"translate-google-api": {
"version": "1.0.4",
@@ -18436,6 +20311,11 @@
"requires": {
"follow-redirects": "^1.10.0"
}
+ },
+ "follow-redirects": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+ "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
}
}
},
@@ -18479,6 +20359,25 @@
"loader-utils": "^1.0.2",
"micromatch": "^3.1.4",
"semver": "^5.0.1"
+ },
+ "dependencies": {
+ "enhanced-resolve": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz",
+ "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.5.0",
+ "tapable": "^1.0.0"
+ }
+ },
+ "tapable": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+ "dev": true
+ }
}
},
"ts-node": {
@@ -18579,6 +20478,13 @@
"to-regex-range": "^5.0.1"
}
},
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -18678,11 +20584,30 @@
}
}
},
+ "tsconfig-paths": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
+ "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+ "dev": true,
+ "requires": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.1",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "dependencies": {
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true
+ }
+ }
+ },
"tslib": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
- "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
- "dev": true
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"tslint": {
"version": "5.20.1",
@@ -18705,11 +20630,29 @@
"tsutils": "^2.29.0"
},
"dependencies": {
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
+ },
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
}
}
},
@@ -18749,6 +20692,14 @@
"dev": true,
"requires": {
"tslib": "^1.8.1"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ }
}
},
"tunnel-agent": {
@@ -18764,12 +20715,6 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
- "type": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
- "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
- "dev": true
- },
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -18784,6 +20729,12 @@
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
},
+ "type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true
+ },
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -18804,9 +20755,9 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"typescript": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
- "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
+ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"dev": true
},
"typescript-collections": {
@@ -18828,6 +20779,11 @@
"vscode-uri": "^1.0.5"
},
"dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@@ -18854,9 +20810,9 @@
"integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0="
},
"ua-parser-js": {
- "version": "0.7.21",
- "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
- "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
+ "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ=="
},
"uglify-js": {
"version": "2.8.29",
@@ -18911,26 +20867,19 @@
}
},
"uid2": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
- "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I="
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
+ "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA=="
},
"unbox-primitive": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
- "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
"requires": {
- "function-bind": "^1.1.1",
- "has-bigints": "^1.0.1",
- "has-symbols": "^1.0.2",
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
"which-boxed-primitive": "^1.0.2"
- },
- "dependencies": {
- "has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
- }
}
},
"unbzip2-stream": {
@@ -18943,32 +20892,14 @@
}
},
"undefsafe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
- "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==",
- "requires": {
- "debug": "^2.2.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- }
- }
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
},
"underscore": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
- "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg=="
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz",
+ "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ=="
},
"unicode-trie": {
"version": "0.3.1",
@@ -18995,21 +20926,8 @@
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
"set-value": "^2.0.1"
- },
- "dependencies": {
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
- }
}
},
- "uniq": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
- "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
- "dev": true
- },
"unique-filename": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
@@ -19135,9 +21053,9 @@
}
},
"uri-js": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
- "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"requires": {
"punycode": "^2.1.0"
}
@@ -19176,19 +21094,9 @@
},
"dependencies": {
"mime": {
- "version": "2.4.4",
- "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
- "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
- },
- "schema-utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
- "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
- "requires": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- }
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
}
}
},
@@ -19226,13 +21134,6 @@
"integrity": "sha512-7/hqDrWa0iMnCoET9W1T07EmD4Eg/Wmoj/X8TGBc++ECRK4m5yTsjP4O6s0yagbxfqIOuUkIxe2/sA+VR2GxZA==",
"requires": {
"fast-deep-equal": "^3.1.3"
- },
- "dependencies": {
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- }
}
},
"use-memo-one": {
@@ -19278,6 +21179,17 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
+ "uuid-js": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/uuid-js/-/uuid-js-0.7.5.tgz",
+ "integrity": "sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA="
+ },
+ "v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "dev": true
+ },
"valid-url": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
@@ -19310,6 +21222,13 @@
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
+ },
+ "dependencies": {
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
+ }
}
},
"void-elements": {
@@ -19318,9 +21237,9 @@
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
},
"vscode-jsonrpc": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz",
- "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A=="
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz",
+ "integrity": "sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ=="
},
"vscode-languageserver": {
"version": "5.3.0-next.10",
@@ -19332,18 +21251,18 @@
}
},
"vscode-languageserver-protocol": {
- "version": "3.15.3",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz",
- "integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==",
+ "version": "3.17.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz",
+ "integrity": "sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg==",
"requires": {
- "vscode-jsonrpc": "^5.0.1",
- "vscode-languageserver-types": "3.15.1"
+ "vscode-jsonrpc": "8.0.1",
+ "vscode-languageserver-types": "3.17.1"
}
},
"vscode-languageserver-types": {
- "version": "3.15.1",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
- "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ=="
+ "version": "3.17.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz",
+ "integrity": "sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ=="
},
"vscode-textbuffer": {
"version": "1.0.0",
@@ -19356,9 +21275,9 @@
"integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ=="
},
"vue-template-compiler": {
- "version": "2.6.11",
- "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz",
- "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==",
+ "version": "2.6.14",
+ "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
+ "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==",
"requires": {
"de-indent": "^1.0.2",
"he": "^1.1.0"
@@ -19387,6 +21306,14 @@
"domexception": "^1.0.1",
"webidl-conversions": "^4.0.2",
"xml-name-validator": "^3.0.0"
+ },
+ "dependencies": {
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true
+ }
}
},
"walkdir": {
@@ -19403,9 +21330,9 @@
}
},
"watchpack": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",
- "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
+ "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
"dev": true,
"requires": {
"glob-to-regexp": "^0.4.1",
@@ -19430,20 +21357,19 @@
}
},
"web-streams-polyfill": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
- "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA=="
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+ "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
},
"webidl-conversions": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
- "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
- "dev": true
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"webpack": {
- "version": "5.70.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz",
- "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==",
+ "version": "5.73.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.73.0.tgz",
+ "integrity": "sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
@@ -19455,13 +21381,13 @@
"acorn-import-assertions": "^1.7.6",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.9.2",
+ "enhanced-resolve": "^5.9.3",
"es-module-lexer": "^0.9.0",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.9",
- "json-parse-better-errors": "^1.0.2",
+ "json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
@@ -19472,54 +21398,31 @@
"webpack-sources": "^3.2.3"
},
"dependencies": {
- "acorn": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
- "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
- "dev": true
- },
- "enhanced-resolve": {
- "version": "5.9.2",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz",
- "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==",
+ "schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"requires": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
}
- },
- "graceful-fs": {
- "version": "4.2.9",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
- "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
- "dev": true
- },
- "neo-async": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
- "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
- "dev": true
- },
- "tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true
}
}
},
"webpack-cli": {
- "version": "4.9.2",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz",
- "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==",
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
- "@webpack-cli/configtest": "^1.1.1",
- "@webpack-cli/info": "^1.4.1",
- "@webpack-cli/serve": "^1.6.1",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
"colorette": "^2.0.14",
"commander": "^7.0.0",
- "execa": "^5.0.0",
+ "cross-spawn": "^7.0.3",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^2.2.0",
@@ -19542,45 +21445,11 @@
"which": "^2.0.1"
}
},
- "execa": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
- "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
- "requires": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^6.0.0",
- "human-signals": "^2.1.0",
- "is-stream": "^2.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.1",
- "onetime": "^5.1.2",
- "signal-exit": "^3.0.3",
- "strip-final-newline": "^2.0.0"
- }
- },
- "get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
- },
"interpret": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
},
- "is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
- },
- "npm-run-path": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
- "requires": {
- "path-key": "^3.0.0"
- }
- },
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -19618,21 +21487,21 @@
}
},
"webpack-dev-middleware": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz",
- "integrity": "sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==",
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
+ "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
"requires": {
"colorette": "^2.0.10",
- "memfs": "^3.4.1",
+ "memfs": "^3.4.3",
"mime-types": "^2.1.31",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"
},
"dependencies": {
"ajv": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz",
- "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+ "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -19646,13 +21515,6 @@
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"requires": {
"fast-deep-equal": "^3.1.3"
- },
- "dependencies": {
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
- }
}
},
"json-schema-traverse": {
@@ -19660,19 +21522,6 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
- "mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
- },
- "mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "requires": {
- "mime-db": "1.52.0"
- }
- },
"schema-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
@@ -19813,17 +21662,6 @@
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
"dev": true
},
- "schema-utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
- "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
- "dev": true,
- "requires": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- }
- },
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -19901,9 +21739,9 @@
"dev": true
},
"html-entities": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz",
- "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==",
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz",
+ "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==",
"dev": true
},
"strip-ansi": {
@@ -19977,12 +21815,23 @@
"dev": true,
"requires": {
"iconv-lite": "0.4.24"
+ },
+ "dependencies": {
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ }
}
},
"whatwg-fetch": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
- "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
+ "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
"whatwg-mimetype": {
"version": "2.3.0",
@@ -19991,16 +21840,19 @@
"dev": true
},
"whatwg-url": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
- "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
- "dev": true,
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
- "lodash.sortby": "^4.7.0",
- "tr46": "^1.0.1",
- "webidl-conversions": "^4.0.2"
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
}
},
+ "when": {
+ "version": "3.7.8",
+ "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz",
+ "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I="
+ },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -20027,103 +21879,21 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"which-pm-runs": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
- "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz",
+ "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="
},
"which-typed-array": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz",
- "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==",
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz",
+ "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==",
"requires": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
- "es-abstract": "^1.18.5",
- "foreach": "^2.0.5",
+ "es-abstract": "^1.20.0",
+ "for-each": "^0.3.3",
"has-tostringtag": "^1.0.0",
- "is-typed-array": "^1.1.7"
- },
- "dependencies": {
- "es-abstract": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
- "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
- "requires": {
- "call-bind": "^1.0.2",
- "es-to-primitive": "^1.2.1",
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.1.1",
- "get-symbol-description": "^1.0.0",
- "has": "^1.0.3",
- "has-symbols": "^1.0.2",
- "internal-slot": "^1.0.3",
- "is-callable": "^1.2.4",
- "is-negative-zero": "^2.0.1",
- "is-regex": "^1.1.4",
- "is-shared-array-buffer": "^1.0.1",
- "is-string": "^1.0.7",
- "is-weakref": "^1.0.1",
- "object-inspect": "^1.11.0",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.2",
- "string.prototype.trimend": "^1.0.4",
- "string.prototype.trimstart": "^1.0.4",
- "unbox-primitive": "^1.0.1"
- }
- },
- "has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
- },
- "is-callable": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
- "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
- },
- "is-regex": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
- "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
- "requires": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- }
- },
- "object-inspect": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
- "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
- },
- "object.assign": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
- "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
- "requires": {
- "call-bind": "^1.0.0",
- "define-properties": "^1.1.3",
- "has-symbols": "^1.0.1",
- "object-keys": "^1.1.1"
- }
- },
- "string.prototype.trimend": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
- "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3"
- }
- },
- "string.prototype.trimstart": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
- "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3"
- }
- }
+ "is-typed-array": "^1.1.9"
}
},
"wide-align": {
@@ -20169,6 +21939,13 @@
"requires": {
"acorn": "^3.1.0",
"acorn-globals": "^3.0.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+ "integrity": "sha512-OLUyIIZ7mF5oaAUT1w0TFqQS81q3saT46x8t7ukpPjMNk+nbs4ZHhs7ToV8EWnLYLepjETXd4XaCE4uxkMeqUw=="
+ }
}
},
"word-wrap": {
@@ -20221,9 +21998,9 @@
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
},
"string-width": {
"version": "3.1.0",
@@ -20250,6 +22027,15 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
+ "write": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^0.5.1"
+ }
+ },
"write-file-atomic": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
@@ -20261,9 +22047,9 @@
}
},
"ws": {
- "version": "7.2.5",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz",
- "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA=="
+ "version": "7.5.8",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz",
+ "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw=="
},
"xdg-basedir": {
"version": "3.0.0",
@@ -20327,14 +22113,14 @@
"dev": true
},
"y18n": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
- "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "1.10.2",
@@ -20359,9 +22145,9 @@
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
},
"string-width": {
"version": "3.1.0",
@@ -20414,7 +22200,7 @@
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
- "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
+ "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
},
"yn": {
"version": "2.0.0",
@@ -20433,9 +22219,9 @@
}
},
"zustand": {
- "version": "3.7.1",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.1.tgz",
- "integrity": "sha512-wHBCZlKj+bg03/hP+Tzv24YhnqqP8MCeN9ECPDXoF01062SIbnfl3j9O0znkDw1lNTY0a8WN3F///a0UhhaEqg=="
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
+ "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA=="
}
}
}
diff --git a/package.json b/package.json
index 71f56e3ce..2e3ef0a07 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
"@types/exif": "^0.6.3",
"@types/express": "^4.17.13",
"@types/express-flash": "0.0.0",
- "@types/express-session": "^1.17.4",
+ "@types/express-session": "^1.17.5",
"@types/express-validator": "^3.0.0",
"@types/file-saver": "^2.0.5",
"@types/google-maps-react": "^2.0.5",
@@ -49,7 +49,7 @@
"@types/mongoose": "^5.11.97",
"@types/node": "^10.17.60",
"@types/nodemailer": "^4.6.6",
- "@types/passport": "^1.0.7",
+ "@types/passport": "^1.0.9",
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-local": "^1.0.34",
"@types/pdfjs-dist": "^2.10.378",
@@ -65,12 +65,15 @@
"@types/prosemirror-transform": "^1.1.5",
"@types/prosemirror-view": "^1.23.1",
"@types/rc-switch": "^1.9.2",
- "@types/react": "^16.14.23",
+ "@types/react": "^18.0.15",
+ "@types/react-icons": "^3.0.0",
+ "@types/react-reconciler": "^0.26.4",
+ "@types/react-transition-group": "^4.4.5",
"@types/react-autosuggest": "^9.3.14",
"@types/react-color": "^2.17.6",
"@types/react-datepicker": "^3.1.8",
- "@types/react-dom": "^16.9.14",
- "@types/react-grid-layout": "^0.17.2",
+ "@types/react-dom": "^18.0.6",
+ "@types/react-grid-layout": "^1.3.2",
"@types/react-measure": "^2.0.8",
"@types/react-select": "^3.1.2",
"@types/react-table": "^6.8.9",
@@ -94,10 +97,21 @@
"cross-env": "^5.2.1",
"css-loader": "^2.1.1",
"dotenv": "^8.6.0",
+ "eslint": "^8.18.0",
+ "eslint-config-airbnb": "^19.0.4",
+ "eslint-config-node": "^4.1.0",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-plugin-import": "^2.26.0",
+ "eslint-plugin-jsx-a11y": "^6.6.0",
+ "eslint-plugin-node": "^11.1.0",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-react": "^7.30.1",
+ "eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^3.0.1",
"fork-ts-checker-webpack-plugin": "^1.6.0",
"jsdom": "^15.2.1",
"mocha": "^5.2.0",
+ "prettier": "^2.7.1",
"sass-loader": "^7.3.1",
"scss-loader": "0.0.1",
"style-loader": "^0.23.1",
@@ -106,17 +120,19 @@
"ts-node-dev": "^1.1.8",
"tslint": "^5.20.1",
"tslint-loader": "^3.6.0",
- "typescript": "^4.6.2",
+ "typescript": "^4.7.4",
"webpack": "^5.69.1",
"webpack-dev-server": "^3.11.3",
"webpack-hot-middleware": "^2.25.1"
},
"dependencies": {
+ "@ffmpeg/core": "0.10.0",
+ "@ffmpeg/ffmpeg": "0.10.0",
"@fortawesome/fontawesome-svg-core": "^1.3.0",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
- "@fortawesome/react-fontawesome": "^0.1.17",
+ "@fortawesome/react-fontawesome": "^0.1.19",
"@hig/flyout": "^1.3.1",
"@hig/theme-context": "^2.1.3",
"@hig/theme-data": "^2.23.1",
@@ -132,13 +148,12 @@
"@types/dom-speech-recognition": "0.0.1",
"@types/formidable": "1.0.31",
"@types/google-maps": "^3.2.3",
- "@types/react-reconciler": "^0.26.4",
"@types/reveal": "^3.3.33",
"@types/supercluster": "^7.1.0",
"@types/three": "^0.126.2",
"@types/web": "0.0.53",
- "@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
"@webscopeio/react-textarea-autocomplete": "^4.9.1",
+ "D": "^1.0.0",
"adm-zip": "^0.4.16",
"archiver": "^3.1.1",
"array-batcher": "^1.2.3",
@@ -153,7 +168,7 @@
"bootstrap": "^4.6.1",
"browser-assert": "^1.2.1",
"bson": "^4.6.1",
- "canvas": "^2.9.0",
+ "canvas": "^2.9.3",
"child_process": "^1.0.2",
"chrome": "^0.1.0",
"class-transformer": "^0.2.0",
@@ -164,7 +179,6 @@
"cookie-parser": "^1.4.6",
"cookie-session": "^2.0.0",
"cors": "^2.8.5",
- "D": "^1.0.0",
"depcheck": "^0.9.2",
"equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
"exif": "^0.6.0",
@@ -174,6 +188,7 @@
"express-session": "^1.17.2",
"express-validator": "^5.3.1",
"expressjs": "^1.0.1",
+ "ffmpeg": "0.0.4",
"file-saver": "^2.0.5",
"find-in-files": "^0.5.0",
"fit-curve": "^0.1.7",
@@ -196,6 +211,7 @@
"https": "^1.0.0",
"https-browserify": "^1.0.0",
"i": "^0.3.7",
+ "iink-js": "^1.5.4",
"image-data-uri": "^2.0.1",
"image-size": "^0.7.5",
"image-size-stream": "^1.1.0",
@@ -225,7 +241,7 @@
"path-browserify": "^1.0.1",
"pdf-parse": "^1.1.1",
"pdfjs": "^2.4.7",
- "pdfjs-dist": "^2.13.216",
+ "pdfjs-dist": "^2.14.305",
"probe-image-size": "^4.0.0",
"process": "^0.11.10",
"prosemirror-commands": "^1.2.1",
@@ -234,35 +250,36 @@
"prosemirror-history": "^1.2.0",
"prosemirror-inputrules": "^1.1.3",
"prosemirror-keymap": "^1.1.5",
- "prosemirror-model": "^1.16.1",
+ "prosemirror-model": "^1.18.1",
"prosemirror-schema-list": "^1.1.6",
- "prosemirror-state": "^1.3.4",
+ "prosemirror-state": "^1.4.1",
"prosemirror-transform": "^1.3.4",
- "prosemirror-view": "^1.23.7",
+ "prosemirror-view": "^1.26.5",
"pug": "^2.0.4",
"puppeteer": "^3.3.0",
"query-string": "^6.14.1",
"raw-loader": "^1.0.0",
"rc-switch": "^1.9.2",
- "react": "^16.14.0",
+ "react": "^18.2.0",
"react-audio-waveform": "0.0.5",
"react-autosuggest": "^9.4.3",
"react-beautiful-dnd": "^13.1.0",
"react-color": "^2.19.3",
"react-compound-slider": "^2.5.0",
"react-datepicker": "^3.8.0",
- "react-dom": "^16.14.0",
- "react-grid-layout": "^0.18.3",
- "react-image-lightbox-with-rotate": "^5.1.1",
+ "react-dom": "^18.2.0",
+ "react-grid-layout": "^1.3.4",
+ "react-icons": "^4.3.1",
"react-jsx-parser": "^1.29.0",
"react-loading": "^2.0.3",
"react-measure": "^2.5.2",
- "react-refresh-typescript": "^2.0.3",
+ "react-refresh-typescript": "^2.0.7",
"react-resizable": "^1.11.1",
"react-resizable-rotatable-draggable": "^0.2.0",
"react-reveal": "^1.2.2",
"react-select": "^3.2.0",
- "react-table": "^7.7.0",
+ "react-table": "^6.11.5",
+ "react-transition-group": "^4.4.2",
"readline": "^1.3.0",
"request": "^2.88.2",
"request-promise": "^4.2.6",
@@ -271,8 +288,8 @@
"serializr": "^1.5.4",
"sharp": "^0.23.4",
"shelljs": "^0.8.5",
- "socket.io": "^2.4.1",
- "socket.io-client": "^2.4.0",
+ "socket.io": "^2.5.0",
+ "socket.io-client": "^2.5.0",
"solr-node": "^1.2.1",
"standard-http-error": "^2.0.1",
"stream-browserify": "^3.0.0",
@@ -289,7 +306,7 @@
"uuid": "^3.4.0",
"valid-url": "^1.0.9",
"web-request": "^1.0.7",
- "webpack-cli": "^4.9.2",
+ "webpack-cli": "^4.10.0",
"webpack-dev-middleware": "^5.3.1",
"webrtc-adapter": "^7.7.1",
"wikijs": "^6.3.3",
diff --git a/src/.DS_Store b/src/.DS_Store
index b0987293b..4751acf44 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index d0d891f77..528a429d0 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,5 +1,5 @@
import v4 = require('uuid/v4');
-import v5 = require("uuid/v5");
+import v5 = require('uuid/v5');
import { ColorState } from 'react-color';
import { Socket } from 'socket.io';
import { Colors } from './client/views/global/globalEnums';
@@ -8,6 +8,7 @@ import Color = require('color');
export namespace Utils {
export let DRAG_THRESHOLD = 4;
+ export let SNAP_THRESHOLD = 10;
export function readUploadedFileAsText(inputFile: File) {
const temporaryFileReader = new FileReader();
@@ -15,7 +16,7 @@ export namespace Utils {
return new Promise((resolve, reject) => {
temporaryFileReader.onerror = () => {
temporaryFileReader.abort();
- reject(new DOMException("Problem parsing input file."));
+ reject(new DOMException('Problem parsing input file.'));
};
temporaryFileReader.onload = () => {
@@ -33,7 +34,7 @@ export namespace Utils {
return v5(seed, v5.URL);
}
- export function GetScreenTransform(ele?: HTMLElement): { scale: number, translateX: number, translateY: number } {
+ export function GetScreenTransform(ele?: HTMLElement): { scale: number; translateX: number; translateY: number } {
if (!ele) {
return { scale: 1, translateX: 1, translateY: 1 };
}
@@ -49,12 +50,12 @@ export namespace Utils {
['log', 'warn'].forEach(function (method) {
const old = (console as any)[method];
(console as any)[method] = function () {
- let stack = new Error("").stack?.split(/\n/);
+ let stack = new Error('').stack?.split(/\n/);
// Chrome includes a single "Error" line, FF doesn't.
if (stack && stack[0].indexOf('Error') === 0) {
stack = stack.slice(1);
}
- const message = (stack?.[1] || "Stack undefined!").trim();
+ const message = (stack?.[1] || 'Stack undefined!').trim();
const args = ([] as any[]).slice.apply(arguments).concat([message]);
return old.apply(console, args);
};
@@ -78,7 +79,7 @@ export namespace Utils {
}
export function CorsProxy(url: string): string {
- return prepend("/corsProxy/") + encodeURIComponent(url);
+ return prepend('/corsProxy/') + encodeURIComponent(url);
}
export function CopyText(text: string) {
@@ -87,14 +88,13 @@ export namespace Utils {
export function decimalToHexString(number: number) {
if (number < 0) {
- number = 0xFFFFFFFF + number + 1;
+ number = 0xffffffff + number + 1;
}
- return (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
+ return (number < 16 ? '0' : '') + number.toString(16).toUpperCase();
}
export function colorString(color: ColorState) {
- return color.hex.startsWith("#") ?
- color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex;
+ return color.hex.startsWith('#') ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex;
}
export function fromRGBAstr(rgba: string) {
@@ -109,8 +109,8 @@ export namespace Utils {
return { r: r, g: g, b: b, a: a };
}
- const isTransparentFunctionHack = "isTransparent(__value__)";
- export const noRecursionHack = "__noRecursion";
+ const isTransparentFunctionHack = 'isTransparent(__value__)';
+ export const noRecursionHack = '__noRecursion';
export function IsRecursiveFilter(val: string) {
return !val.includes(noRecursionHack);
}
@@ -119,18 +119,18 @@ export namespace Utils {
}
export function IsTransparentFilter() {
// bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
- return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:check`;// bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
+ return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:check`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
}
export function IsOpaqueFilter() {
// bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
- return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`;// bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
+ return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
}
export function PropUnsetFilter(prop: string) {
return `${prop}:any,${noRecursionHack}:unset`;
}
- export function toRGBAstr(col: { r: number, g: number, b: number, a?: number }) {
- return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")";
+ export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
+ return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
}
export function HSLtoRGB(h: number, s: number, l: number) {
@@ -139,23 +139,35 @@ export namespace Utils {
// l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s,
- x = c * (1 - Math.abs((h / 60) % 2 - 1)),
+ x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
m = l - c / 2;
let r = 0,
g = 0,
b = 0;
if (0 <= h && h < 60) {
- r = c; g = x; b = 0;
+ r = c;
+ g = x;
+ b = 0;
} else if (60 <= h && h < 120) {
- r = x; g = c; b = 0;
+ r = x;
+ g = c;
+ b = 0;
} else if (120 <= h && h < 180) {
- r = 0; g = c; b = x;
+ r = 0;
+ g = c;
+ b = x;
} else if (180 <= h && h < 240) {
- r = 0; g = x; b = c;
+ r = 0;
+ g = x;
+ b = c;
} else if (240 <= h && h < 300) {
- r = x; g = 0; b = c;
+ r = x;
+ g = 0;
+ b = c;
} else if (300 <= h && h < 360) {
- r = c; g = 0; b = x;
+ r = c;
+ g = 0;
+ b = x;
}
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
@@ -204,11 +216,11 @@ export namespace Utils {
return { h: h, s: s, l: l };
}
- export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number) {
- if (scrollTop + contextHgt < targetY + minSpacing + targetHgt) {
+ export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) {
+ if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) {
return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
}
- if (scrollTop > targetY - minSpacing - targetHgt) {
+ if (scrollTop >= Math.max(0, targetY - minSpacing - targetHgt)) {
return Math.max(0, Math.floor(targetY - minSpacing - targetHgt));
}
}
@@ -218,20 +230,19 @@ export namespace Utils {
}
export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] {
- if ((xs2 < xs && xe2 > xs) || (xs2 < xe && xe2 > xe) || (xs2 > xs && xe2 < xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
- if (xe2 < xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
- //if (xs2 > xe)
+ if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
+ if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
+ //if (xs2 > xe)
return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]];
}
export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] {
- if ((ys2 < ys && ye2 > ys) || (ys2 < ye && ye2 > ye) || (ys2 > ys && ye2 < ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
- if (ye2 < ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
- //if (ys2 > ye)
+ if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
+ if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
+ //if (ys2 > ye)
return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
}
function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) {
-
if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 };
const atob = { x: bx - ax, y: by - ay };
const atop = { x: px - ax, y: py - ay };
@@ -244,30 +255,41 @@ export namespace Utils {
return {
point: {
x: ax + atob.x * t,
- y: ay + atob.y * t
+ y: ay + atob.y * t,
},
left: dot < 1,
dot: dot,
- t: t
+ t: t,
};
}
- export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number,
- l1: number, t1: number, w1: number, h1: number,
- x: number, y: number) {
+ export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number, l1: number, t1: number, w1: number, h1: number, x: number, y: number) {
const r = l + w,
b = t + h;
const r1 = l1 + w1,
b1 = t1 + h1;
- const hsegs = [[l, r, t, l1, r1, t1], [l, r, b, l1, r1, t1], [l, r, t, l1, r1, b1], [l, r, b, l1, r1, b1]];
- const vsegs = [[l, t, b, l1, t1, b1], [r, t, b, l1, t1, b1], [l, t, b, r1, t1, b1], [r, t, b, r1, t1, b1]];
- const res = hsegs.reduce((closest, seg) => {
- const res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
- return (res[0] < closest[0]) ? res : closest;
- }, [Number.MAX_VALUE, []] as [number, number[]]);
+ const hsegs = [
+ [l, r, t, l1, r1, t1],
+ [l, r, b, l1, r1, t1],
+ [l, r, t, l1, r1, b1],
+ [l, r, b, l1, r1, b1],
+ ];
+ const vsegs = [
+ [l, t, b, l1, t1, b1],
+ [r, t, b, l1, t1, b1],
+ [l, t, b, r1, t1, b1],
+ [r, t, b, r1, t1, b1],
+ ];
+ const res = hsegs.reduce(
+ (closest, seg) => {
+ const res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
+ return res[0] < closest[0] ? res : closest;
+ },
+ [Number.MAX_VALUE, []] as [number, number[]]
+ );
const fres = vsegs.reduce((closest, seg) => {
const res = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
- return (res[0] < closest[0]) ? res : closest;
+ return res[0] < closest[0] ? res : closest;
}, res);
const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
@@ -278,8 +300,7 @@ export namespace Utils {
const r = l + w,
b = t + h;
- x = clamp(x, l, r),
- y = clamp(y, t, b);
+ (x = clamp(x, l, r)), (y = clamp(y, t, b));
const dl = Math.abs(x - l),
dr = Math.abs(x - r),
@@ -288,18 +309,18 @@ export namespace Utils {
const m = Math.min(dl, dr, dt, db);
- return (m === dt) ? [x, t] :
- (m === db) ? [x, b] :
- (m === dl) ? [l, y] : [r, y];
+ return m === dt ? [x, t] : m === db ? [x, b] : m === dl ? [l, y] : [r, y];
}
export function GetClipboardText(): string {
- const textArea = document.createElement("textarea");
+ const textArea = document.createElement('textarea');
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
- try { document.execCommand('paste'); } catch (err) { }
+ try {
+ document.execCommand('paste');
+ } catch (err) {}
const val = textArea.value;
document.body.removeChild(textArea);
@@ -317,7 +338,7 @@ export namespace Utils {
if (logFilter !== undefined && logFilter !== message.type) {
return;
}
- const idString = (message.id || "").padStart(36, ' ');
+ const idString = (message.id || '').padStart(36, ' ');
prefix = prefix.padEnd(16, ' ');
console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `);
}
@@ -330,14 +351,14 @@ export namespace Utils {
}
export function Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) {
- log("Emit", message.Name, args, false);
+ log('Emit', message.Name, args, false);
socket.emit(message.Message, args);
}
export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>;
export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
- log("Emit", message.Name, args, false);
+ log('Emit', message.Name, args, false);
if (fn) {
socket.emit(message.Message, args, loggingCallback('Receiving', fn, message.Name));
} else {
@@ -357,30 +378,33 @@ export namespace Utils {
}
export type RoomHandler = (socket: Socket, room: string) => any;
export type UsedSockets = Socket | SocketIOClient.Socket;
- export type RoomMessage = "create or join" | "created" | "joined";
+ export type RoomMessage = 'create or join' | 'created' | 'joined';
export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
socket.on(message, room => handler(socket, room));
}
}
-export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any, extract: any } {
+export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } {
const omit: any = { ...obj };
const extract: any = {};
keys.forEach(key => {
extract[key] = omit[key];
delete omit[key];
});
- pattern && Array.from(Object.keys(omit)).filter(key => key.match(pattern)).forEach(key => {
- extract[key] = omit[key];
- delete omit[key];
- });
+ pattern &&
+ Array.from(Object.keys(omit))
+ .filter(key => key.match(pattern))
+ .forEach(key => {
+ extract[key] = omit[key];
+ delete omit[key];
+ });
addKeyFunc?.(omit);
return { omit, extract };
}
export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
const dup: any = {};
- keys.forEach(key => dup[key] = obj[key]);
+ keys.forEach(key => (dup[key] = obj[key]));
addKeyFunc && addKeyFunc(dup);
return dup;
}
@@ -399,26 +423,41 @@ export function timenow() {
return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
}
+export function incrementTitleCopy(title: string) {
+ const numstr = title.match(/.*(\{([0-9]*)\})+/);
+ const copyNumStr = `{${1 + (numstr ? +numstr[2] : 0)}}`;
+ return (numstr ? title.replace(numstr[1], '') : title) + copyNumStr;
+}
+
export function formatTime(time: number) {
time = Math.round(time);
const hours = Math.floor(time / 60 / 60);
- const minutes = Math.floor(time / 60) - (hours * 60);
+ const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
- return (hours ? hours.toString() + ":" : "") + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
+ return (hours ? hours.toString() + ':' : '') + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
}
-export function aggregateBounds(boundsList: { x: number, y: number, width?: number, height?: number }[], xpad: number, ypad: number) {
- const bounds = boundsList.map(b => ({ x: b.x, y: b.y, r: b.x + (b.width || 0), b: b.y + (b.height || 0) })).reduce((bounds, b) => ({
- x: Math.min(b.x, bounds.x), y: Math.min(b.y, bounds.y),
- r: Math.max(b.r, bounds.r), b: Math.max(b.b, bounds.b)
- }), { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
+// x is furthest left, y is furthest top, r is furthest right, b is furthest bottom
+export function aggregateBounds(boundsList: { x: number; y: number; width?: number; height?: number }[], xpad: number, ypad: number) {
+ const bounds = boundsList
+ .map(b => ({ x: b.x, y: b.y, r: b.x + (b.width || 0), b: b.y + (b.height || 0) }))
+ .reduce(
+ (bounds, b) => ({
+ x: Math.min(b.x, bounds.x),
+ y: Math.min(b.y, bounds.y),
+ r: Math.max(b.r, bounds.r),
+ b: Math.max(b.b, bounds.b),
+ }),
+ { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }
+ );
return {
- x: bounds.x !== Number.MAX_VALUE ? bounds.x - xpad : bounds.x, y: bounds.y !== Number.MAX_VALUE ? bounds.y - ypad : bounds.y,
- r: bounds.r !== -Number.MAX_VALUE ? bounds.r + xpad : bounds.r, b: bounds.b !== -Number.MAX_VALUE ? bounds.b + ypad : bounds.b
+ x: bounds.x !== Number.MAX_VALUE ? bounds.x - xpad : bounds.x,
+ y: bounds.y !== Number.MAX_VALUE ? bounds.y - ypad : bounds.y,
+ r: bounds.r !== -Number.MAX_VALUE ? bounds.r + xpad : bounds.r,
+ b: bounds.b !== -Number.MAX_VALUE ? bounds.b + ypad : bounds.b,
};
}
-export function intersectRect(r1: { left: number, top: number, width: number, height: number },
- r2: { left: number, top: number, width: number, height: number }) {
+export function intersectRect(r1: { left: number; top: number; width: number; height: number }, r2: { left: number; top: number; width: number; height: number }) {
return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
}
@@ -426,32 +465,63 @@ export function percent2frac(percent: string) {
return Number(percent.substr(0, percent.length - 1)) / 100;
}
-export function numberRange(num: number) { return num > 0 && num < 1000 ? Array.from(Array(num)).map((v, i) => i) : []; }
+export function numberRange(num: number) {
+ return num > 0 && num < 1000 ? Array.from(Array(num)).map((v, i) => i) : [];
+}
+
+export function returnTransparent() {
+ return 'transparent';
+}
+
+export function returnTrue() {
+ return true;
+}
-export function returnTransparent() { return "transparent"; }
+export function returnFalse() {
+ return false;
+}
-export function returnTrue() { return true; }
+export function returnAll() {
+ return 'all';
+}
-export function returnFalse() { return false; }
+export function returnNone() {
+ return 'none';
+}
-export function returnVal(val1?: number, val2?: number) { return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0; }
+export function returnVal(val1?: number, val2?: number) {
+ return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0;
+}
-export function returnOne() { return 1; }
+export function returnOne() {
+ return 1;
+}
-export function returnZero() { return 0; }
+export function returnZero() {
+ return 0;
+}
-export function returnEmptyString() { return ""; }
+export function returnEmptyString() {
+ return '';
+}
-export function returnEmptyFilter() { return [] as string[]; }
+export function returnEmptyFilter() {
+ return [] as string[];
+}
-export function returnEmptyDoclist() { return [] as any[]; }
+export function returnEmptyDoclist() {
+ return [] as any[];
+}
export let emptyPath = [];
-export function emptyFunction() { return undefined; }
-
-export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); }
+export function emptyFunction() {
+ return undefined;
+}
+export function unimplementedFunction() {
+ throw new Error('This function is not implemented, but should be.');
+}
export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@@ -472,7 +542,6 @@ export function DeepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) {
}
export namespace JSONUtils {
-
export function tryParse(source: string) {
let results: any;
try {
@@ -482,7 +551,6 @@ export namespace JSONUtils {
}
return results;
}
-
}
const easeInOutQuad = (currentTime: number, start: number, change: number, duration: number) => {
@@ -497,32 +565,57 @@ const easeInOutQuad = (currentTime: number, start: number, change: number, durat
};
export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number) {
- const elements = (element instanceof HTMLElement ? [element] : element);
+ const elements = element instanceof HTMLElement ? [element] : element;
const starts = elements.map(element => element.scrollTop);
const startDate = new Date().getTime();
const animateScroll = () => {
const currentDate = new Date().getTime();
const currentTime = currentDate - startDate;
- elements.map((element, i) => element.scrollTop = easeInOutQuad(currentTime, starts[i], to - starts[i], duration));
+ elements.map((element, i) => (element.scrollTop = easeInOutQuad(currentTime, starts[i], to - starts[i], duration)));
+
+ if (currentTime < duration) {
+ requestAnimationFrame(animateScroll);
+ } else {
+ elements.forEach(element => (element.scrollTop = to));
+ }
+ };
+ animateScroll();
+}
+
+export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) {
+ const elements = element instanceof HTMLElement ? [element] : element;
+ const starts = elements.map(element => element.scrollLeft);
+ const startDate = new Date().getTime();
+
+ const animateScroll = () => {
+ const currentDate = new Date().getTime();
+ const currentTime = currentDate - startDate;
+ elements.map((element, i) => (element.scrollLeft = easeInOutQuad(currentTime, starts[i], to - starts[i], duration)));
if (currentTime < duration) {
requestAnimationFrame(animateScroll);
} else {
- elements.forEach(element => element.scrollTop = to);
+ elements.forEach(element => (element.scrollLeft = to));
}
};
animateScroll();
}
-export function addStyleSheet(styleType: string = "text/css") {
- const style = document.createElement("style");
+
+export function addStyleSheet(styleType: string = 'text/css') {
+ const style = document.createElement('style');
style.type = styleType;
const sheets = document.head.appendChild(style);
return (sheets as any).sheet;
}
-export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = ".") {
- const propText = typeof css === "string" ? css : Object.keys(css).map(p => p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p])).join(";");
- return sheet.insertRule(selectorPrefix + selector + "{" + propText + "}", sheet.cssRules.length);
+export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
+ const propText =
+ typeof css === 'string'
+ ? css
+ : Object.keys(css)
+ .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p]))
+ .join(';');
+ return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
}
export function removeStyleSheetRule(sheet: any, rule: number) {
if (sheet.rules.length) {
@@ -541,33 +634,35 @@ export function clearStyleSheetRules(sheet: any) {
export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) {
if (!element) return;
- ["pointerdown", "pointerup"].map(event => element.dispatchEvent(
- new PointerEvent(event, {
+ ['pointerdown', 'pointerup'].map(event => {
+ const me = new PointerEvent(event, {
view: window,
bubbles: true,
cancelable: true,
button: 2,
- pointerType: "mouse",
+ pointerType: 'mouse',
clientX: x,
clientY: y,
screenX: sx,
screenY: sy,
- })));
+ });
+ (me as any).dash = true;
+ element.dispatchEvent(me);
+ });
if (rightClick) {
- const me =
- new MouseEvent("contextmenu", {
- view: window,
- bubbles: true,
- cancelable: true,
- button: 2,
- clientX: x,
- clientY: y,
- movementX: 0,
- movementY: 0,
- screenX: sx,
- screenY: sy,
- });
+ const me = new MouseEvent('contextmenu', {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ button: 2,
+ clientX: x,
+ clientY: y,
+ movementX: 0,
+ movementY: 0,
+ screenX: sx,
+ screenY: sy,
+ });
(me as any).dash = true;
element.dispatchEvent(me);
}
@@ -575,25 +670,25 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe
export function DashColor(color: string) {
try {
- return color ? Color(color.toLowerCase()) : Color("transparent");
+ return color ? Color(color.toLowerCase()) : Color('transparent');
} catch (e) {
- console.log("COLOR error:", e);
- return Color("red");
+ console.log('COLOR error:', e);
+ return Color('red');
}
}
export function lightOrDark(color: any) {
- const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) :
- color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color;
+ if (color === 'transparent') return 'gray';
+ if (color.startsWith?.('linear')) return 'black';
+ const nonAlphaColor = color.startsWith('#') ? (color as string).substring(0, 7) : color.startsWith('rgba') ? color.replace(/,.[^,]*\)/, ')').replace('rgba', 'rgb') : color;
const col = DashColor(nonAlphaColor).rgb();
- const colsum = (col.red() + col.green() + col.blue());
+ const colsum = col.red() + col.green() + col.blue();
if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
else return Colors.WHITE;
}
-
export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
- if (elem.tagName === "INPUT") return "input";
+ if (elem.tagName === 'INPUT') return 'input';
if (elem.nodeType === elem.TEXT_NODE) {
const range = elem.ownerDocument.createRange();
range.selectNodeContents(elem);
@@ -603,12 +698,11 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi
range.setStart(elem, currentPos);
range.setEnd(elem, currentPos + 1);
const rangeRect = range.getBoundingClientRect();
- if (rangeRect.left <= x && rangeRect.right >= x &&
- rangeRect.top <= y && rangeRect.bottom >= y) {
- range.expand?.("word"); // doesn't exist in firefox
+ if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
+ range.expand?.('word'); // doesn't exist in firefox
const ret = range.toString();
range.detach();
- return (ret);
+ return ret;
}
currentPos += 1;
}
@@ -617,8 +711,7 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi
const range = childNode.ownerDocument.createRange();
range.selectNodeContents(childNode);
const rangeRect = range.getBoundingClientRect();
- if (rangeRect.left <= x && rangeRect.right >= x &&
- rangeRect.top <= y && rangeRect.bottom >= y) {
+ if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
range.detach();
const word = getWordAtPoint(childNode, x, y);
if (word) return word;
@@ -643,6 +736,23 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
e.preventDefault();
}
+/**
+ * Helper method for converting pixel string eg. '32px' into number eg. 32
+ * @param value: string with 'px' ending
+ * @returns value: number
+ *
+ * Example:
+ * '32px' -> 32
+ */
+export function numberValue(value: string | undefined): number {
+ if (value == undefined) return 0;
+ return parseInt(value);
+}
+
+export function numbersAlmostEqual(num1: number, num2: number) {
+ return Math.abs(num1 - num2) < 0.2;
+}
+
export function setupMoveUpEvents(
target: object,
e: React.PointerEvent,
@@ -654,10 +764,11 @@ export function setupMoveUpEvents(
noDoubleTapTimeout?: () => void
) {
const doubleTapTimeout = 300;
- (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < doubleTapTimeout);
+ (target as any)._doubleTap = Date.now() - (target as any)._lastTap < doubleTapTimeout;
(target as any)._lastTap = Date.now();
(target as any)._downX = (target as any)._lastX = e.clientX;
(target as any)._downY = (target as any)._lastY = e.clientY;
+ (target as any)._noClick = false;
const _moveEvent = (e: PointerEvent): void => {
if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
@@ -665,10 +776,9 @@ export function setupMoveUpEvents(
clearTimeout((target as any)._doubleTime);
(target as any)._doubleTime = undefined;
}
- if (moveEvent(e, [(target as any)._downX, (target as any)._downY],
- [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) {
- document.removeEventListener("pointermove", _moveEvent);
- document.removeEventListener("pointerup", _upEvent);
+ if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) {
+ document.removeEventListener('pointermove', _moveEvent);
+ document.removeEventListener('pointerup', _upEvent);
}
}
(target as any)._lastX = e.clientX;
@@ -689,17 +799,20 @@ export function setupMoveUpEvents(
clearTimeout((target as any)._doubleTime);
(target as any)._doubleTime = undefined;
}
- clickEvent(e, (target as any)._doubleTap);
+ (target as any)._noClick = clickEvent(e, (target as any)._doubleTap);
}
- document.removeEventListener("pointermove", _moveEvent);
- document.removeEventListener("pointerup", _upEvent);
+ document.removeEventListener('pointermove', _moveEvent);
+ document.removeEventListener('pointerup', _upEvent);
+ };
+ const _clickEvent = (e: MouseEvent): void => {
+ if ((target as any)._noClick) e.stopPropagation();
+ document.removeEventListener('click', _clickEvent, true);
};
if (stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
- document.removeEventListener("pointermove", _moveEvent);
- document.removeEventListener("pointerup", _upEvent);
- document.addEventListener("pointermove", _moveEvent);
- document.addEventListener("pointerup", _upEvent);
-} \ No newline at end of file
+ document.addEventListener('pointermove', _moveEvent);
+ document.addEventListener('pointerup', _upEvent);
+ document.addEventListener('click', _clickEvent, true);
+}
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index e498a7cca..5a34fcf11 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,27 +1,27 @@
-import * as io from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, UpdatingFromServer, updateCachedAcls } from '../fields/Doc';
-import { Utils, emptyFunction } from '../Utils';
-import { SerializationHelper } from './util/SerializationHelper';
-import { RefField } from '../fields/RefField';
-import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols';
-import { GestureOverlay } from './views/GestureOverlay';
-import MobileInkOverlay from '../mobile/MobileInkOverlay';
import { runInAction } from 'mobx';
+import * as rp from 'request-promise';
+import * as io from 'socket.io-client';
+import { Doc, Opt, UpdatingFromServer } from '../fields/Doc';
+import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
import { ObjectField } from '../fields/ObjectField';
+import { RefField } from '../fields/RefField';
import { StrCast } from '../fields/Types';
-import * as rp from 'request-promise';
+import MobileInkOverlay from '../mobile/MobileInkOverlay';
+import { emptyFunction, Utils } from '../Utils';
+import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
+import { SerializationHelper } from './util/SerializationHelper';
+import { GestureOverlay } from './views/GestureOverlay';
/**
* This class encapsulates the transfer and cross-client synchronization of
* data stored only in documents (RefFields). In the process, it also
* creates and maintains a cache of documents so that they can be accessed
* more efficiently. Currently, there is no cache eviction scheme in place.
- *
+ *
* NOTE: while this class is technically abstracted to work with any [RefField], because
* [Doc] instances are the only [RefField] we need / have implemented at the moment, the documentation
* will treat all data used here as [Doc]s
- *
+ *
* Any time we want to write a new field to the database (via the server)
* or update ourselves based on the server's update message, that occurs here
*/
@@ -32,12 +32,12 @@ export namespace DocServer {
const strings: string[] = [];
Array.from(Object.keys(_cache)).forEach(key => {
const doc = _cache[key];
- if (doc instanceof Doc) strings.push(StrCast(doc.author) + " " + StrCast(doc.title) + " " + StrCast(Doc.GetT(doc, "title", "string", true)));
+ if (doc instanceof Doc) strings.push(StrCast(doc.author) + ' ' + StrCast(doc.title) + ' ' + StrCast(Doc.GetT(doc, 'title', 'string', true)));
});
- print && strings.sort().forEach((str, i) => console.log(i.toString() + " " + str));
- rp.post(Utils.prepend("/setCacheDocumentIds"), {
+ print && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+ rp.post(Utils.prepend('/setCacheDocumentIds'), {
body: {
- cacheDocumentIds: Array.from(Object.keys(_cache)).join(";"),
+ cacheDocumentIds: Array.from(Object.keys(_cache)).join(';'),
},
json: true,
});
@@ -50,8 +50,8 @@ export namespace DocServer {
export enum WriteMode {
Default = 0, //Anything goes
Playground = 1, //Playground (write own/no read other updates)
- LiveReadonly = 2,//Live Readonly (no write/read others)
- LivePlayground = 3,//Live Playground (write own/read others)
+ LiveReadonly = 2, //Live Readonly (no write/read others)
+ LivePlayground = 3, //Live Playground (write own/read others)
}
const fieldWriteModes: { [field: string]: WriteMode } = {};
const docsWithUpdates: { [field: string]: Set<Doc> } = {};
@@ -62,7 +62,7 @@ export namespace DocServer {
livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground));
}
export function IsPlaygroundField(field: string) {
- return DocServer.PlaygroundFields?.includes(field.replace(/^_/, ""));
+ return DocServer.PlaygroundFields?.includes(field.replace(/^_/, ''));
}
export function setFieldWriteMode(field: string, writeMode: WriteMode) {
@@ -77,13 +77,13 @@ export namespace DocServer {
}
export function getFieldWriteMode(field: string) {
- return fieldWriteModes[field] || WriteMode.Default;
+ return Doc.CurrentUserEmail === 'guest' ? WriteMode.LiveReadonly : fieldWriteModes[field] || WriteMode.Default;
}
export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
let list = docsWithUpdates[field];
if (!list) {
- list = docsWithUpdates[field] = new Set;
+ list = docsWithUpdates[field] = new Set();
}
if (!list.has(doc)) {
Doc.AddCachedUpdate(doc, field, oldValue);
@@ -92,7 +92,6 @@ export namespace DocServer {
}
export namespace Mobile {
-
export function dispatchGesturePoints(content: GestureContent) {
Utils.Emit(_socket, MessageStore.GesturePoints, content);
}
@@ -111,17 +110,17 @@ export namespace DocServer {
}
}
- const instructions = "This page will automatically refresh after this alert is closed. Expect to reconnect after about 30 seconds.";
+ const instructions = 'This page will automatically refresh after this alert is closed. Expect to reconnect after about 30 seconds.';
function alertUser(connectionTerminationReason: string) {
switch (connectionTerminationReason) {
- case "crash":
+ case 'crash':
alert(`Dash has temporarily crashed. Administrators have been notified and the server is restarting itself. ${instructions}`);
break;
- case "temporary":
+ case 'temporary':
alert(`An administrator has chosen to restart the server. ${instructions}`);
break;
- case "exit":
- alert("An administrator has chosen to kill the server. Do not expect to reconnect until administrators start the server.");
+ case 'exit':
+ alert('An administrator has chosen to kill the server. Do not expect to reconnect until administrators start the server.');
break;
default:
console.log(`Received an unknown ConnectionTerminated message: ${connectionTerminationReason}`);
@@ -132,7 +131,7 @@ export namespace DocServer {
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
GUID = identifier;
- protocol = protocol.startsWith("https") ? "wss" : "ws";
+ protocol = protocol.startsWith('https') ? 'wss' : 'ws';
_socket = io.connect(`${protocol}://${hostname}:${port}`);
// io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
@@ -153,17 +152,17 @@ export namespace DocServer {
Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
// mobile ink overlay socket events to communicate between mobile view and desktop view
- _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => {
+ _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
MobileInkOverlay.Instance.drawStroke(content);
});
- _socket.addEventListener("receiveOverlayTrigger", (content: MobileInkOverlayContent) => {
+ _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => {
GestureOverlay.Instance.enableMobileInkOverlay(content);
MobileInkOverlay.Instance.initMobileInkOverlay(content);
});
- _socket.addEventListener("receiveUpdateOverlayPosition", (content: UpdateMobileInkOverlayPositionContent) => {
+ _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => {
MobileInkOverlay.Instance.updatePosition(content);
});
- _socket.addEventListener("receiveMobileDocumentUpload", (content: MobileDocumentUploadContent) => {
+ _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => {
MobileInkOverlay.Instance.uploadDocument(content);
});
}
@@ -173,14 +172,13 @@ export namespace DocServer {
}
export namespace Control {
-
let _isReadOnly = false;
export function makeReadOnly() {
if (!_isReadOnly) {
_isReadOnly = true;
- _CreateField = field => _cache[field[Id]] = field;
+ _CreateField = field => (_cache[field[Id]] = field);
_UpdateField = emptyFunction;
- _RespondToUpdate = emptyFunction;
+ // _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
}
}
@@ -195,8 +193,9 @@ export namespace DocServer {
}
}
- export function isReadOnly() { return _isReadOnly; }
-
+ export function isReadOnly() {
+ return _isReadOnly;
+ }
}
/**
@@ -209,7 +208,6 @@ export namespace DocServer {
}
export namespace Util {
-
/**
* Emits a message to the server that wipes
* all documents in the database.
@@ -217,7 +215,6 @@ export namespace DocServer {
export function deleteDatabase() {
Utils.Emit(_socket, MessageStore.DeleteAll, {});
}
-
}
// RETRIEVE DOCS FROM SERVER
@@ -258,8 +255,7 @@ export namespace DocServer {
});
cached[UpdatingFromServer] = false;
return cached;
- }
- else if (field !== undefined) {
+ } else if (field !== undefined) {
_cache[id] = field;
} else {
delete _cache[id];
@@ -272,7 +268,7 @@ export namespace DocServer {
// here, indicate that the document associated with this id is currently
// being retrieved and cached
!force && (_cache[id] = deserializeField);
- return force ? cached as any : deserializeField;
+ return force ? (cached as any) : deserializeField;
} else if (cached instanceof Promise) {
// BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
// and requested the document I'm looking for. Shouldn't fetch again, just
@@ -316,7 +312,6 @@ export namespace DocServer {
Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.VideoDetails, videoIds: videoIds }, callBack);
}
-
/**
* Given a list of Doc GUIDs, this utility function will asynchronously attempt to each id's associated
* field, first looking in the RefField cache and then communicating with
@@ -364,40 +359,36 @@ export namespace DocServer {
const proms: Promise<void>[] = [];
runInAction(() => {
for (const field of fields) {
- if (field !== undefined && field !== null && !_cache[field.id]) {
+ const cached = _cache[field.id];
+ if (!cached) {
// deserialize
- const cached = _cache[field.id];
- if (!cached) {
- const prom = SerializationHelper.Deserialize(field).then(deserialized => {
- fieldMap[field.id] = deserialized;
-
- //overwrite or delete any promises (that we inserted as flags
- // to indicate that the field was in the process of being fetched). Now everything
- // should be an actual value within or entirely absent from the cache.
- if (deserialized !== undefined) {
- _cache[field.id] = deserialized;
- } else {
- delete _cache[field.id];
- }
- return deserialized;
- });
- // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
- // we set the value at the field's id to a promise that will resolve to the field.
- // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
- // The mapping in the .then call ensures that when other callers await these promises, they'll
- // get the resolved field
- _cache[field.id] = prom;
-
- // adds to a list of promises that will be awaited asynchronously
- proms.push(prom);
- } else if (cached instanceof Promise) {
- proms.push(cached as any);
- }
- } else if (_cache[field.id] instanceof Promise) {
- proms.push(_cache[field.id] as any);
- (_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f);
+ const prom = SerializationHelper.Deserialize(field).then(deserialized => {
+ fieldMap[field.id] = deserialized;
+
+ //overwrite or delete any promises (that we inserted as flags
+ // to indicate that the field was in the process of being fetched). Now everything
+ // should be an actual value within or entirely absent from the cache.
+ if (deserialized !== undefined) {
+ _cache[field.id] = deserialized;
+ } else {
+ delete _cache[field.id];
+ }
+ return deserialized;
+ });
+ // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+ // we set the value at the field's id to a promise that will resolve to the field.
+ // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+ // The mapping in the .then call ensures that when other callers await these promises, they'll
+ // get the resolved field
+ _cache[field.id] = prom;
+
+ // adds to a list of promises that will be awaited asynchronously
+ proms.push(prom);
+ } else if (cached instanceof Promise) {
+ proms.push(cached as any);
+ cached.then((f: any) => (fieldMap[field.id] = f));
} else if (field) {
- proms.push(_cache[field.id] as any);
+ proms.push(cached as any);
fieldMap[field.id] = DocServer.GetCachedRefField(field.id) || field;
}
}
@@ -417,20 +408,19 @@ export namespace DocServer {
const field = fields[id];
map[id] = field;
});
-
}
// 7) those promises we encountered in the else if of 1), which represent
// other callers having already submitted a request to the server for (a) document(s)
// in which we're interested, must still be awaited so that we can return the proper
- // values for those as well.
+ // values for those as well.
//
// fortunately, those other callers will also hit their own version of 6) and clean up
// the shared cache when these promises resolve, so all we have to do is...
const otherCallersFetching = await Promise.all(promises);
// ...extract the RefFields returned from the resolution of those promises and add them to our
// own map.
- waitingIds.forEach((id, index) => map[id] = otherCallersFetching[index]);
+ waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index]));
// now, we return our completed mapping from all of the ids that were passed into the method
// to their actual RefField | undefined values. This return value either becomes the input
@@ -480,7 +470,7 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- (!DocServer.Control.isReadOnly()) && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ !DocServer.Control.isReadOnly() && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
let _UpdateField: (id: string, diff: any) => void = errorFunc;
@@ -524,12 +514,11 @@ export namespace DocServer {
Utils.Emit(_socket, MessageStore.DeleteFields, ids);
}
-
function _respondToDeleteImpl(ids: string | string[]) {
function deleteId(id: string) {
delete _cache[id];
}
- if (typeof ids === "string") {
+ if (typeof ids === 'string') {
deleteId(ids);
} else if (Array.isArray(ids)) {
ids.map(deleteId);
diff --git a/src/client/Network.ts b/src/client/Network.ts
index bf2918734..c781d4b6b 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -4,46 +4,54 @@ import { Upload } from "../server/SharedMediaTypes";
export namespace Networking {
- export async function FetchFromServer(relativeRoute: string) {
- return (await fetch(relativeRoute)).text();
- }
+ export async function FetchFromServer(relativeRoute: string) {
+ return (await fetch(relativeRoute)).text();
+ }
- export async function PostToServer(relativeRoute: string, body?: any) {
- const options = {
- uri: Utils.prepend(relativeRoute),
- method: "POST",
- body,
- json: true
- };
- return requestPromise.post(options);
- }
+ export async function PostToServer(relativeRoute: string, body?: any) {
+ const options = {
+ uri: Utils.prepend(relativeRoute),
+ method: "POST",
+ body,
+ json: true
+ };
+ return requestPromise.post(options);
+ }
- export async function UploadFilesToServer<T extends Upload.FileInformation = Upload.FileInformation>(files: File | File[]): Promise<Upload.FileResponse<T>[]> {
- const formData = new FormData();
- if (Array.isArray(files)) {
- if (!files.length) {
- return [];
+ /**
+ * Handles uploading basic file types to server and makes the API call to "/uploadFormData" endpoint
+ * with the mapping of GUID to filem as parameters.
+ *
+ * @param files the files to be uploaded to the server
+ * @returns the response as a json from the server
+ */
+ export async function UploadFilesToServer<T extends Upload.FileInformation = Upload.FileInformation>(files: File | File[]): Promise<Upload.FileResponse<T>[]> {
+ const formData = new FormData();
+ if (Array.isArray(files)) {
+ if (!files.length) {
+ return [];
+ }
+ files.forEach(file => formData.append(Utils.GenerateGuid(), file));
+ } else {
+ formData.append(Utils.GenerateGuid(), files);
}
- files.forEach(file => formData.append(Utils.GenerateGuid(), file));
- } else {
- formData.append(Utils.GenerateGuid(), files);
- }
- const parameters = {
- method: 'POST',
- body: formData
- };
- const response = await fetch("/uploadFormData", parameters);
- return response.json();
- }
-
- export async function UploadYoutubeToServer<T extends Upload.FileInformation = Upload.FileInformation>(videoId: string): Promise<Upload.FileResponse<T>[]> {
- const parameters = {
- method: 'POST',
- body: JSON.stringify({ videoId }),
- json: true
- };
- const response = await fetch("/uploadYoutubeVideo", parameters);
- return response.json();
- }
+ const parameters = {
+ method: 'POST',
+ body: formData
+ };
+ const response = await fetch("/uploadFormData", parameters);
+ return response.json();
+ }
+
+ export async function UploadYoutubeToServer<T extends Upload.FileInformation = Upload.FileInformation>(videoId: string): Promise<Upload.FileResponse<T>[]> {
+ const parameters = {
+ method: 'POST',
+ body: JSON.stringify({ videoId }),
+ json: true
+ };
+ const response = await fetch("/uploadYoutubeVideo", parameters);
+ return response.json();
+ }
+
} \ No newline at end of file
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index c5ff2db68..05879a247 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -1,17 +1,16 @@
import { action, observable, runInAction } from 'mobx';
-import { observer } from "mobx-react";
-import { Doc, DocListCastAsync } from "../../../fields/Doc";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Utils } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { Docs } from "../../documents/Documents";
-import { DocumentDecorations } from "../../views/DocumentDecorations";
-import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
-import "../../views/nodes/WebBox.scss";
-import "./YoutubeBox.scss";
-import React = require("react");
+import { observer } from 'mobx-react';
+import { Doc, DocListCastAsync } from '../../../fields/Doc';
import { InkTool } from '../../../fields/InkField';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Utils } from '../../../Utils';
+import { DocServer } from '../../DocServer';
+import { Docs } from '../../documents/Documents';
+import { DocumentDecorations } from '../../views/DocumentDecorations';
+import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
+import '../../views/nodes/WebBox.scss';
+import './YoutubeBox.scss';
+import React = require('react');
interface VideoTemplate {
thumbnailUrl: string;
@@ -29,19 +28,19 @@ interface VideoTemplate {
*/
@observer
export class YoutubeBox extends React.Component<FieldViewProps> {
-
@observable YoutubeSearchElement: HTMLInputElement | undefined;
@observable searchResultsFound: boolean = false;
@observable searchResults: any[] = [];
@observable videoClicked: boolean = false;
- @observable selectedVideoUrl: string = "";
+ @observable selectedVideoUrl: string = '';
@observable lisOfBackUp: JSX.Element[] = [];
@observable videoIds: string | undefined;
@observable videoDetails: any[] = [];
@observable curVideoTemplates: VideoTemplate[] = [];
-
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(YoutubeBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(YoutubeBox, fieldKey);
+ }
/**
* When component mounts, last search's results are laoded in based on the back up stored
@@ -54,19 +53,15 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
const castedDetailBackUp = Cast(this.props.Document.cachedDetails, Doc);
const awaitedDetails = await castedDetailBackUp;
-
if (awaitedBackUp) {
-
-
const jsonList = await DocListCastAsync(awaitedBackUp.json);
const jsonDetailList = await DocListCastAsync(awaitedDetails!.json);
if (jsonList!.length !== 0) {
- runInAction(() => this.searchResultsFound = true);
+ runInAction(() => (this.searchResultsFound = true));
let index = 0;
//getting the necessary information from backUps and building templates that will be used to map in render
for (const video of jsonList!) {
-
const videoId = await Cast(video.id, Doc);
const id = StrCast(videoId!.videoId);
const snippet = await Cast(video.snippet, Doc);
@@ -75,10 +70,10 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
const thumbnailMedium = await Cast(thumbnail!.medium, Doc);
const thumbnailUrl = StrCast(thumbnailMedium!.url);
const videoDescription = StrCast(snippet!.description);
- const pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!;
+ const pusblishDate = this.roundPublishTime(StrCast(snippet!.publishedAt))!;
const channelTitle = StrCast(snippet!.channelTitle);
- let duration: string = "";
- let viewCount: string = "";
+ let duration: string = '';
+ let viewCount: string = '';
if (jsonDetailList!.length !== 0) {
const contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc);
const statistics = await Cast(jsonDetailList![index].statistics, Doc);
@@ -86,7 +81,16 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!;
}
index = index + 1;
- const newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration, viewCount: viewCount };
+ const newTemplate: VideoTemplate = {
+ videoId: id,
+ videoTitle: videoTitle,
+ thumbnailUrl: thumbnailUrl,
+ publishDate: pusblishDate,
+ channelTitle: channelTitle,
+ videoDescription: videoDescription,
+ duration: duration,
+ viewCount: viewCount,
+ };
runInAction(() => this.curVideoTemplates.push(newTemplate));
}
}
@@ -96,20 +100,20 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
_ignore = 0;
onPreWheel = (e: React.WheelEvent) => {
this._ignore = e.timeStamp;
- }
+ };
onPrePointer = (e: React.PointerEvent) => {
this._ignore = e.timeStamp;
- }
+ };
onPostPointer = (e: React.PointerEvent) => {
if (this._ignore !== e.timeStamp) {
e.stopPropagation();
}
- }
+ };
onPostWheel = (e: React.WheelEvent) => {
if (this._ignore !== e.timeStamp) {
e.stopPropagation();
}
- }
+ };
/**
* Function that submits the title entered by user on enter press.
@@ -117,12 +121,11 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
onEnterKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode === 13) {
const submittedTitle = this.YoutubeSearchElement!.value;
- this.YoutubeSearchElement!.value = "";
+ this.YoutubeSearchElement!.value = '';
this.YoutubeSearchElement!.blur();
DocServer.getYoutubeVideos(submittedTitle, this.processesVideoResults);
-
}
- }
+ };
/**
* The callback that is passed in to server, which functions as a way to
@@ -134,12 +137,12 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
this.searchResults = videos;
if (this.searchResults.length > 0) {
this.searchResultsFound = true;
- this.videoIds = "";
- videos.forEach((video) => {
- if (this.videoIds === "") {
+ this.videoIds = '';
+ videos.forEach(video => {
+ if (this.videoIds === '') {
this.videoIds = video.id.videoId;
} else {
- this.videoIds = this.videoIds! + ", " + video.id.videoId;
+ this.videoIds = this.videoIds! + ', ' + video.id.videoId;
}
});
//Asking for details that include duration and viewCount from server for videoIds
@@ -149,7 +152,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
this.videoClicked = false;
}
}
- }
+ };
/**
* The callback that is given to server to process and receive returned details about the videos.
@@ -157,28 +160,26 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
@action
processVideoDetails = (videoDetails: any[]) => {
this.videoDetails = videoDetails;
- this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: "detailBackUp" });
- }
+ this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: 'detailBackUp' });
+ };
/**
* The function that stores the search results in the props document.
*/
backUpSearchResults = (videos: any[]) => {
- this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: "videosBackUp" });
- }
+ this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: 'videosBackUp' });
+ };
/**
* The function that filters out escaped characters returned by the api
* in the title of the videos.
*/
filterYoutubeTitleResult = (resultTitle: string) => {
- let processedTitle: string = resultTitle.replace(/&amp;/g, "&");//.ReplaceAll("&amp;", "&");
+ let processedTitle: string = resultTitle.replace(/&amp;/g, '&'); //.ReplaceAll("&amp;", "&");
processedTitle = processedTitle.replace(/"&#39;/g, "'");
- processedTitle = processedTitle.replace(/&quot;/g, "\"");
+ processedTitle = processedTitle.replace(/&quot;/g, '"');
return processedTitle;
- }
-
-
+ };
/**
* The function that converts ISO date, which is passed in, to normal date and finds the
@@ -195,7 +196,6 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
const totalMonths = totalDays / 30.417;
const totalYears = totalMonths / 12;
-
const truncYears = Math.trunc(totalYears);
const truncMonths = Math.trunc(totalMonths);
const truncDays = Math.trunc(totalDays);
@@ -203,62 +203,61 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
const truncMin = Math.trunc(totalMin);
const truncSec = Math.trunc(totalSeconds);
- let pluralCase = "";
+ let pluralCase = '';
if (truncYears !== 0) {
- truncYears > 1 ? pluralCase = "s" : pluralCase = "";
- return truncYears + " year" + pluralCase + " ago";
+ truncYears > 1 ? (pluralCase = 's') : (pluralCase = '');
+ return truncYears + ' year' + pluralCase + ' ago';
} else if (truncMonths !== 0) {
- truncMonths > 1 ? pluralCase = "s" : pluralCase = "";
- return truncMonths + " month" + pluralCase + " ago";
+ truncMonths > 1 ? (pluralCase = 's') : (pluralCase = '');
+ return truncMonths + ' month' + pluralCase + ' ago';
} else if (truncDays !== 0) {
- truncDays > 1 ? pluralCase = "s" : pluralCase = "";
- return truncDays + " day" + pluralCase + " ago";
+ truncDays > 1 ? (pluralCase = 's') : (pluralCase = '');
+ return truncDays + ' day' + pluralCase + ' ago';
} else if (truncHours !== 0) {
- truncHours > 1 ? pluralCase = "s" : pluralCase = "";
- return truncHours + " hour" + pluralCase + " ago";
+ truncHours > 1 ? (pluralCase = 's') : (pluralCase = '');
+ return truncHours + ' hour' + pluralCase + ' ago';
} else if (truncMin !== 0) {
- truncMin > 1 ? pluralCase = "s" : pluralCase = "";
- return truncMin + " minute" + pluralCase + " ago";
+ truncMin > 1 ? (pluralCase = 's') : (pluralCase = '');
+ return truncMin + ' minute' + pluralCase + ' ago';
} else if (truncSec !== 0) {
- truncSec > 1 ? pluralCase = "s" : pluralCase = "";
- return truncSec + " second" + pluralCase + " ago";
+ truncSec > 1 ? (pluralCase = 's') : (pluralCase = '');
+ return truncSec + ' second' + pluralCase + ' ago';
}
- }
+ };
/**
* The function that converts the passed in ISO time to normal duration time.
*/
convertIsoTimeToDuration = (isoDur: string) => {
-
- const convertedTime = isoDur.replace(/D|H|M/g, ":").replace(/P|T|S/g, "").split(":");
+ const convertedTime = isoDur.replace(/D|H|M/g, ':').replace(/P|T|S/g, '').split(':');
if (1 === convertedTime.length) {
- 2 !== convertedTime[0].length && (convertedTime[0] = "0" + convertedTime[0]), convertedTime[0] = "0:" + convertedTime[0];
+ 2 !== convertedTime[0].length && (convertedTime[0] = '0' + convertedTime[0]), (convertedTime[0] = '0:' + convertedTime[0]);
} else {
for (var r = 1, l = convertedTime.length - 1; l >= r; r++) {
- 2 !== convertedTime[r].length && (convertedTime[r] = "0" + convertedTime[r]);
+ 2 !== convertedTime[r].length && (convertedTime[r] = '0' + convertedTime[r]);
}
}
- return convertedTime.join(":");
- }
+ return convertedTime.join(':');
+ };
/**
- * The function that rounds the viewCount to the nearest
+ * The function that rounds the viewCount to the nearest
* thousand, million or billion, given a viewCount number.
*/
abbreviateViewCount = (viewCount: number) => {
if (viewCount < 1000) {
return viewCount.toString();
} else if (viewCount >= 1000 && viewCount < 1000000) {
- return (Math.trunc(viewCount / 1000)) + "K";
+ return Math.trunc(viewCount / 1000) + 'K';
} else if (viewCount >= 1000000 && viewCount < 1000000000) {
- return (Math.trunc(viewCount / 1000000)) + "M";
+ return Math.trunc(viewCount / 1000000) + 'M';
} else if (viewCount >= 1000000000) {
- return (Math.trunc(viewCount / 1000000000)) + "B";
+ return Math.trunc(viewCount / 1000000000) + 'B';
}
- }
+ };
/**
* The function that is called to decide on what'll be rendered by the component.
@@ -268,63 +267,69 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
renderSearchResultsOrVideo = () => {
if (this.searchResultsFound) {
if (this.searchResults.length !== 0) {
- return <ul>
- {this.searchResults.map((video, index) => {
- const filteredTitle = this.filterYoutubeTitleResult(video.snippet.title);
- const channelTitle = video.snippet.channelTitle;
- const videoDescription = video.snippet.description;
- const pusblishDate = this.roundPublishTime(video.snippet.publishedAt);
- let duration;
- let viewCount;
- if (this.videoDetails.length !== 0) {
- duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration);
- viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount);
- }
-
-
- return <li onClick={() => this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}>
- <div className="search_wrapper">
- <div style={{ backgroundColor: "yellow" }}>
- <img src={video.snippet.thumbnails.medium.url} />
- <span className="video_duration">{duration}</span>
- </div>
- <div className="textual_info">
- <span className="videoTitle">{filteredTitle}</span>
- <span className="channelName">{channelTitle}</span>
- <span className="viewCount">{viewCount}</span>
- <span className="publish_time">{pusblishDate}</span>
- <p className="video_description">{videoDescription}</p>
-
- </div>
- </div>
- </li>;
- })}
- </ul>;
+ return (
+ <ul>
+ {this.searchResults.map((video, index) => {
+ const filteredTitle = this.filterYoutubeTitleResult(video.snippet.title);
+ const channelTitle = video.snippet.channelTitle;
+ const videoDescription = video.snippet.description;
+ const pusblishDate = this.roundPublishTime(video.snippet.publishedAt);
+ let duration;
+ let viewCount;
+ if (this.videoDetails.length !== 0) {
+ duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration);
+ viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount);
+ }
+
+ return (
+ <li onClick={() => this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}>
+ <div className="search_wrapper">
+ <div style={{ backgroundColor: 'yellow' }}>
+ <img src={video.snippet.thumbnails.medium.url} />
+ <span className="video_duration">{duration}</span>
+ </div>
+ <div className="textual_info">
+ <span className="videoTitle">{filteredTitle}</span>
+ <span className="channelName">{channelTitle}</span>
+ <span className="viewCount">{viewCount}</span>
+ <span className="publish_time">{pusblishDate}</span>
+ <p className="video_description">{videoDescription}</p>
+ </div>
+ </div>
+ </li>
+ );
+ })}
+ </ul>
+ );
} else if (this.curVideoTemplates.length !== 0) {
- return <ul>
- {this.curVideoTemplates.map((video: VideoTemplate) => {
- return <li onClick={() => this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}>
- <div className="search_wrapper">
- <div style={{ backgroundColor: "yellow" }}>
- <img src={video.thumbnailUrl} />
- <span className="video_duration">{video.duration}</span>
- </div>
- <div className="textual_info">
- <span className="videoTitle">{video.videoTitle}</span>
- <span className="channelName">{video.channelTitle}</span>
- <span className="viewCount">{video.viewCount}</span>
- <span className="publish_time">{video.publishDate}</span>
- <p className="video_description">{video.videoDescription}</p>
- </div>
- </div>
- </li>;
- })}
- </ul>;
+ return (
+ <ul>
+ {this.curVideoTemplates.map((video: VideoTemplate) => {
+ return (
+ <li onClick={() => this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}>
+ <div className="search_wrapper">
+ <div style={{ backgroundColor: 'yellow' }}>
+ <img src={video.thumbnailUrl} />
+ <span className="video_duration">{video.duration}</span>
+ </div>
+ <div className="textual_info">
+ <span className="videoTitle">{video.videoTitle}</span>
+ <span className="channelName">{video.channelTitle}</span>
+ <span className="viewCount">{video.viewCount}</span>
+ <span className="publish_time">{video.publishDate}</span>
+ <p className="video_description">{video.videoDescription}</p>
+ </div>
+ </div>
+ </li>
+ );
+ })}
+ </ul>
+ );
}
} else {
- return (null);
+ return null;
}
- }
+ };
/**
* Given a videoId and title, creates a new youtube embedded url, and uses that
@@ -332,7 +337,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
*/
@action
embedVideoOnClick = (videoId: string, filteredTitle: string) => {
- const embeddedUrl = "https://www.youtube.com/embed/" + videoId;
+ const embeddedUrl = 'https://www.youtube.com/embed/' + videoId;
this.selectedVideoUrl = embeddedUrl;
const addFunction = this.props.addDocument!;
const newVideoX = NumCast(this.props.Document.x);
@@ -340,24 +345,24 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, _width: 400, _height: 315, x: newVideoX, y: newVideoY }));
this.videoClicked = true;
- }
+ };
render() {
- const content =
- <div className="youtubeBox-cont" style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- <input type="text" placeholder="Search for a video" onKeyDown={this.onEnterKeyDown} style={{ height: 40, width: "100%", border: "1px solid black", padding: 5, textAlign: "center" }} ref={(e) => this.YoutubeSearchElement = e!} />
+ const content = (
+ <div className="youtubeBox-cont" style={{ width: '100%', height: '100%', position: 'absolute' }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
+ <input type="text" placeholder="Search for a video" onKeyDown={this.onEnterKeyDown} style={{ height: 40, width: '100%', border: '1px solid black', padding: 5, textAlign: 'center' }} ref={e => (this.YoutubeSearchElement = e!)} />
{this.renderSearchResultsOrVideo()}
- </div>;
+ </div>
+ );
const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
- const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+ const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? '-interactive' : '');
return (
<>
- <div className={classname} >
- {content}
- </div>
- {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
- </>);
+ <div className={classname}>{content}</div>
+ {!frozen ? null : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
+ </>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 161dff6e0..9dfadf778 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -1,44 +1,67 @@
export enum DocumentType {
- NONE = "none",
+ NONE = 'none',
// core data types
- RTF = "rtf",
- IMG = "image",
- WEB = "web",
- COL = "collection",
- KVP = "kvp",
- VID = "video",
- AUDIO = "audio",
- PDF = "pdf",
- INK = "inks",
- SCREENSHOT = "screenshot",
- FONTICON = "fonticonbox",
- FILTER = "filter",
- SEARCH = "search", // search query
- LABEL = "label", // simple text label
- BUTTON = "button", // onClick button
- WEBCAM = "webcam", // webcam
- MARKER = "marker", // generic marker document not intended to be viewed independently of its context (e.g., for text selections in PDF/Web/RTF)
- DATE = "date", // calendar view of a date
- SCRIPTING = "script", // script editor
- EQUATION = "equation", // equation editor
- FUNCPLOT = "funcplot", // function plotter
- MAP = "map",
+ RTF = 'rtf',
+ IMG = 'image',
+ WEB = 'web',
+ COL = 'collection',
+ KVP = 'kvp',
+ VID = 'video',
+ AUDIO = 'audio',
+ REC = 'recording',
+ PDF = 'pdf',
+ INK = 'inks',
+ SCREENSHOT = 'screenshot',
+ FONTICON = 'fonticonbox',
+ FILTER = 'filter',
+ SEARCH = 'search', // search query
+ LABEL = 'label', // simple text label
+ BUTTON = 'button', // onClick button
+ WEBCAM = 'webcam', // webcam
+ MARKER = 'marker', // generic marker document not intended to be viewed independently of its context (e.g., for text selections in PDF/Web/RTF)
+ DATE = 'date', // calendar view of a date
+ SCRIPTING = 'script', // script editor
+ EQUATION = 'equation', // equation editor
+ FUNCPLOT = 'funcplot', // function plotter
+ MAP = 'map',
+ DATAVIZ = 'dataviz',
// special purpose wrappers that either take no data or are compositions of lower level types
- LINK = "link",
- LINKANCHOR = "linkanchor",
- IMPORT = "import",
- SLIDER = "slider",
- PRES = "presentation",
- PRESELEMENT = "preselement",
- COLOR = "color",
- YOUTUBE = "youtube",
- SEARCHITEM = "searchitem",
- COMPARISON = "comparison",
- GROUP = "group",
+ LINK = 'link',
+ LINKANCHOR = 'linkanchor',
+ IMPORT = 'import',
+ SLIDER = 'slider',
+ PRES = 'presentation',
+ PRESELEMENT = 'preselement',
+ COLOR = 'color',
+ YOUTUBE = 'youtube',
+ SEARCHITEM = 'searchitem',
+ COMPARISON = 'comparison',
+ GROUP = 'group',
- LINKDB = "linkdb", // database of links ??? why do we have this
- SCRIPTDB = "scriptdb", // database of scripts
- GROUPDB = "groupdb", // database of groups
-} \ No newline at end of file
+ LINKDB = 'linkdb', // database of links ??? why do we have this
+ SCRIPTDB = 'scriptdb', // database of scripts
+ GROUPDB = 'groupdb', // database of groups
+}
+export enum CollectionViewType {
+ Invalid = 'invalid',
+ Freeform = 'freeform',
+ Schema = 'schema',
+ Docking = 'docking',
+ Tree = 'tree',
+ Stacking = 'stacking',
+ Masonry = 'masonry',
+ Multicolumn = 'multicolumn',
+ Multirow = 'multirow',
+ Time = 'time',
+ Carousel = 'carousel',
+ Carousel3D = '3D Carousel',
+ Linear = 'linear',
+ //Staff = "staff",
+ Map = 'map',
+ Grid = 'grid',
+ Pile = 'pileup',
+ StackedTimeline = 'stacked timeline',
+ NoteTaking = "notetaking"
+}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2e9484db4..9d00664ad 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,131 +1,170 @@
-import { action, runInAction } from "mobx";
-import { basename } from "path";
-import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { HtmlField } from "../../fields/HtmlField";
-import { InkField } from "../../fields/InkField";
-import { List } from "../../fields/List";
-import { ProxyField } from "../../fields/Proxy";
-import { RichTextField } from "../../fields/RichTextField";
-import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
-import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../fields/Types";
-import { AudioField, ImageField, MapField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
-import { SharingPermissions } from "../../fields/util";
-import { Upload } from "../../server/SharedMediaTypes";
-import { OmitKeys, Utils } from "../../Utils";
-import { YoutubeBox } from "../apis/youtube/YoutubeBox";
-import { DocServer } from "../DocServer";
-import { Networking } from "../Network";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { DocumentManager } from "../util/DocumentManager";
-import { dropActionType } from "../util/DragManager";
-import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox";
-import { LinkManager } from "../util/LinkManager";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { undoBatch, UndoManager } from "../util/UndoManager";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
-import { ContextMenu } from "../views/ContextMenu";
-import { ContextMenuProps } from "../views/ContextMenuItem";
-import { DFLT_IMAGE_NATIVE_DIM } from "../views/global/globalCssVariables.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from "../views/InkingStroke";
-import { AudioBox } from "../views/nodes/AudioBox";
-import { ColorBox } from "../views/nodes/ColorBox";
-import { ComparisonBox } from "../views/nodes/ComparisonBox";
-import { DocFocusOptions } from "../views/nodes/DocumentView";
-import { EquationBox } from "../views/nodes/EquationBox";
-import { FieldViewProps } from "../views/nodes/FieldView";
-import { FilterBox } from "../views/nodes/FilterBox";
-import { FontIconBox } from "../views/nodes/button/FontIconBox";
-import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
-import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox";
-import { ImageBox } from "../views/nodes/ImageBox";
-import { KeyValueBox } from "../views/nodes/KeyValueBox";
-import { LabelBox } from "../views/nodes/LabelBox";
-import { LinkBox } from "../views/nodes/LinkBox";
-import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup";
-import { PDFBox } from "../views/nodes/PDFBox";
-import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
-import { ScriptingBox } from "../views/nodes/ScriptingBox";
-import { SliderBox } from "../views/nodes/SliderBox";
-import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import { PresBox } from "../views/nodes/trails/PresBox";
-import { PresElementBox } from "../views/nodes/trails/PresElementBox";
-import { VideoBox } from "../views/nodes/VideoBox";
-import { WebBox } from "../views/nodes/WebBox";
-import { SearchBox } from "../views/search/SearchBox";
-import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
-import { DocumentType } from "./DocumentTypes";
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { MapBox } from "../views/nodes/MapBox/MapBox";
-
-const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { action, runInAction } from 'mobx';
+import { basename } from 'path';
+import { DateField } from '../../fields/DateField';
+import { Doc, DocListCast, DocListCastAsync, Field, Initializing, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { HtmlField } from '../../fields/HtmlField';
+import { InkField, PointData } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
+import { RichTextField } from '../../fields/RichTextField';
+import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
+import { ComputedField, ScriptField } from '../../fields/ScriptField';
+import { Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
+import { inheritParentAcls, SharingPermissions } from '../../fields/util';
+import { Upload } from '../../server/SharedMediaTypes';
+import { aggregateBounds, OmitKeys, Utils } from '../../Utils';
+import { YoutubeBox } from '../apis/youtube/YoutubeBox';
+import { DocServer } from '../DocServer';
+import { Networking } from '../Network';
+import { DocumentManager } from '../util/DocumentManager';
+import { DragManager, dropActionType } from '../util/DragManager';
+import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox';
+import { LinkManager } from '../util/LinkManager';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { undoBatch, UndoManager } from '../util/UndoManager';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
+import { CollectionView } from '../views/collections/CollectionView';
+import { ContextMenu } from '../views/ContextMenu';
+import { ContextMenuProps } from '../views/ContextMenuItem';
+import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from '../views/InkingStroke';
+import { AudioBox } from '../views/nodes/AudioBox';
+import { FontIconBox } from '../views/nodes/button/FontIconBox';
+import { ColorBox } from '../views/nodes/ColorBox';
+import { ComparisonBox } from '../views/nodes/ComparisonBox';
+import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
+import { DocFocusOptions } from '../views/nodes/DocumentView';
+import { EquationBox } from '../views/nodes/EquationBox';
+import { FieldViewProps } from '../views/nodes/FieldView';
+import { FilterBox } from '../views/nodes/FilterBox';
+import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
+import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
+import { ImageBox } from '../views/nodes/ImageBox';
+import { KeyValueBox } from '../views/nodes/KeyValueBox';
+import { LabelBox } from '../views/nodes/LabelBox';
+import { LinkBox } from '../views/nodes/LinkBox';
+import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup';
+import { MapBox } from '../views/nodes/MapBox/MapBox';
+import { PDFBox } from '../views/nodes/PDFBox';
+import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox';
+import { ScreenshotBox } from '../views/nodes/ScreenshotBox';
+import { ScriptingBox } from '../views/nodes/ScriptingBox';
+import { SliderBox } from '../views/nodes/SliderBox';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import { PresBox } from '../views/nodes/trails/PresBox';
+import { PresElementBox } from '../views/nodes/trails/PresElementBox';
+import { VideoBox } from '../views/nodes/VideoBox';
+import { WebBox } from '../views/nodes/WebBox';
+import { SearchBox } from '../views/search/SearchBox';
+import { CollectionViewType, DocumentType } from './DocumentTypes';
+
+const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
class EmptyBox {
public static LayoutString() {
- return "";
+ return '';
}
}
-abstract class FInfo {
- description: string = "";
- type?: string;
+export abstract class FInfo {
+ description: string = '';
+ fieldType?: string;
values?: Field[];
- layoutField?: boolean; // is this field a layout (or datadoc) field
// format?: string; // format to display values (e.g, decimal places, $, etc)
// parse?: ScriptField; // parse a value from a string
- constructor(d: string, l: boolean = false) { this.description = d; this.layoutField = l; }
+ constructor(d: string) {
+ this.description = d;
+ }
+}
+class BoolInfo extends FInfo {
+ fieldType? = 'boolean';
+ values?: boolean[] = [true, false];
+}
+class NumInfo extends FInfo {
+ fieldType? = 'number';
+ values?: number[] = [];
+ constructor(d: string, values?: number[]) {
+ super(d);
+ this.values = values;
+ }
+}
+class StrInfo extends FInfo {
+ fieldType? = 'string';
+ values?: string[] = [];
+ constructor(d: string, values?: string[]) {
+ super(d);
+ this.values = values;
+ }
+}
+class DocInfo extends FInfo {
+ fieldType? = 'Doc';
+ values?: Doc[] = [];
+ constructor(d: string, values?: Doc[]) {
+ super(d);
+ this.values = values;
+ }
+}
+class DimInfo extends FInfo {
+ fieldType? = 'DimUnit';
+ values? = [DimUnit.Pixel, DimUnit.Ratio];
+}
+class PEInfo extends FInfo {
+ fieldType? = 'pointerEvents';
+ values? = ['all', 'none'];
+}
+class DAInfo extends FInfo {
+ fieldType? = 'dropActionType';
+ values? = ['alias', 'copy', 'move', 'same', 'proto', 'none'];
}
-class BoolInfo extends FInfo { type?= "boolean"; values?: boolean[] = [true, false]; }
-class NumInfo extends FInfo { type?= "number"; values?: number[] = []; }
-class StrInfo extends FInfo { type?= "string"; values?: string[] = []; }
-class DocInfo extends FInfo { type?= "Doc"; values?: Doc[] = []; }
-class DimInfo extends FInfo { type?= "DimUnit"; values?= [DimUnit.Pixel, DimUnit.Ratio]; }
-class PEInfo extends FInfo { type?= "pointerEvents"; values?= ["all", "none"]; }
-class DAInfo extends FInfo { type?= "dropActionType"; values?= ["alias", "copy", "move", "same", "proto", "none"]; }
type BOOLt = BoolInfo | boolean;
type NUMt = NumInfo | number;
type STRt = StrInfo | string;
type DOCt = DocInfo | Doc;
type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio;
-type PEVt = PEInfo | "none" | "all";
+type PEVt = PEInfo | 'none' | 'all';
type DROPt = DAInfo | dropActionType;
export class DocumentOptions {
- system?: BOOLt = new BoolInfo("is this a system created/owned doc");
+ x?: NUMt = new NumInfo('x coordinate of document in a freeform view');
+ y?: NUMt = new NumInfo('y coordinage of document in a freeform view');
+ z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', [1, 0]);
+ system?: BOOLt = new BoolInfo('is this a system created/owned doc');
+ type?: STRt = new StrInfo('type of document', Array.from(Object.keys(DocumentType)));
+ title?: string;
_dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
- allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?");
+ allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?');
childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection ");
- targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? ");
- color?: string; // foreground color data doc
- backgroundColor?: STRt = new StrInfo("background color for data doc");
- _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true);
- _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents", true);
- _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title", true);
- _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes", true);
- _headerPointerEvents?: PEVt = new PEInfo("types of events the header of a custom text document can consume", true);
- _panX?: NUMt = new NumInfo("horizontal pan location of a freeform view", true);
- _panY?: NUMt = new NumInfo("vertical pan location of a freeform view", true);
- _width?: NUMt = new NumInfo("displayed width of a document", true);
- _height?: NUMt = new NumInfo("displayed height of document", true);
- _nativeWidth?: NUMt = new NumInfo("native width of document contents (e.g., the pixel width of an image)", true);
- _nativeHeight?: NUMt = new NumInfo("native height of document contents (e.g., the pixel height of an image)", true);
- _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", true);
- _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units", true);
- _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)", true);
- _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
+ targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? ');
+ userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
+ color?: STRt = new StrInfo('foreground color data doc');
+ backgroundColor?: STRt = new StrInfo('background color for data doc');
+ _autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
+ _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title');
+ _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes');
+ _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
+ _panX?: NUMt = new NumInfo('horizontal pan location of a freeform view');
+ _panY?: NUMt = new NumInfo('vertical pan location of a freeform view');
+ _width?: NUMt = new NumInfo('displayed width of a document');
+ _height?: NUMt = new NumInfo('displayed height of document');
+ _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)');
+ _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)');
+ _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height");
+ _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
+ _fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)');
+ _fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
+ _contentBounds?: List<number>; // the (forced) bounds of the document to display. format is: [left, top, right, bottom]
_lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
_lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
- _freeformLOD?: boolean; // whether to use LOD to render a freeform document
+ _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target
_showTitle?: string; // field name to display in header (:hover is an optional suffix)
_showCaption?: string; // which field to display in the caption area. leave empty to have no caption
_scrollTop?: number; // scroll location for pdfs
- _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged
+ _noAutoscroll?: boolean; // whether collections autoscroll when this item is dragged
_chromeHidden?: boolean; // whether the editing chrome for a document is hidden
- _layerTags?: List<string>; // layer tags a document has (used for tab filtering "layers" in document tab)
_searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view)
_forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active)
- _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere
+ _stayInCollection?: boolean; // whether the document should remain in its collection when someone tries to drag and drop it elsewhere
_raiseWhenDragged?: boolean; // whether a document is brought to front when dragged.
_hideContextMenu?: boolean; // whether the context menu can be shown
_viewType?: string; // sub type of a collection
@@ -137,8 +176,10 @@ export class DocumentOptions {
_xPadding?: number;
_yPadding?: number;
_itemIndex?: number; // which item index the carousel viewer is showing
- _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts
+ _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts
_singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document)
+ _minFontSize?: number; // minimum font size for labelBoxes
+ _maxFontSize?: number; // maximum font size for labelBoxes
_columnWidth?: number;
_columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
_fontSize?: string;
@@ -151,43 +192,57 @@ export class DocumentOptions {
_timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays)
_timecodeToHide?: number; // the time that a document should be hidden
_timelineLabel?: boolean; // whether the document exists on a timeline
- "_carousel-caption-xMargin"?: number;
- "_carousel-caption-yMargin"?: number;
- x?: number;
- y?: number;
- z?: number; // whether document is in overlay (1) or not (0 or undefined)
+ '_carousel-caption-xMargin'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection');
+ '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection');
+ 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view');
+ 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view');
+ 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)');
lat?: number;
lng?: number;
infoWindowOpen?: boolean;
author?: string;
_layoutKey?: string;
+ fieldValues?: List<any>; // possible field values used by fieldInfos
+ fieldType?: string; // type of afield used by fieldInfos
unrendered?: boolean; // denotes an annotation that is not rendered with a DocumentView (e.g, rtf/pdf text selections and links to scroll locations in web/pdf)
- type?: string;
- title?: string;
- "acl-Public"?: string; // public permissions
- "_acl-Public"?: string; // public permissions
+ 'acl-Public'?: string; // public permissions
+ '_acl-Public'?: string; // public permissions
version?: string; // version identifier for a document
label?: string;
hidden?: boolean;
_hidden?: boolean;
- mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing"
+ pointerEvents?: string; // pointer events that the documentview should have
+ mediaState?: string; // status of audio/video media document: "pendingRecording", "recording", "paused", "playing"
+ recording?: boolean; // whether WebCam is recording or not
autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline.
- dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it.
+ dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it.
toolTip?: string; // tooltip to display on hover
+ contextMenuFilters?: List<ScriptField>;
+ contextMenuScripts?: List<ScriptField>;
+ contextMenuLabels?: List<string>;
+ contextMenuIcons?: List<string>;
dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel)
description?: string; // added for links
layout?: string | Doc; // default layout string for a document
- contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
+ contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width
childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view)
childLayoutString?: string; // template string for collection to use to render its children
+ childDocumentsActive?: boolean; // whether child documents are active when parent is document active
childDontRegisterViews?: boolean;
childHideLinkButton?: boolean; // hide link buttons on all children
+ childContextMenuFilters?: List<ScriptField>;
+ childContextMenuScripts?: List<ScriptField>;
+ childContextMenuLabels?: List<string>;
+ childContextMenuIcons?: List<string>;
hideLinkButton?: boolean; // whether the blue link counter button should be hidden
+ hideDecorationTitle?: boolean;
+ hideOpenButton?: boolean;
+ hideResizeHandles?: boolean;
+ hideDocumentButtonBar?: boolean;
hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden
isTemplateForField?: string; // the field key for which the containing document is a rendering template
isTemplateDoc?: boolean;
- watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count
targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
templates?: List<string>;
hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images)
@@ -209,20 +264,15 @@ export class DocumentOptions {
baseProto?: boolean; // is this a base prototoype
dontRegisterView?: boolean;
lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document. see PresBox.
- "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form
- "onChildDoubleClick-rawScript"?: string; // onChildDoubleClick script in raw text form
- "onChildClick-rawScript"?: string; // on ChildClick script in raw text form
- "onClick-rawScript"?: string; // onClick script in raw text form
- "onCheckedClick-rawScript"?: string; // onChecked script in raw text form
- "onCheckedClick-params"?: List<string>; // parameter list for onChecked treeview functions
columnHeaders?: List<SchemaHeaderField>; // headers for stacking views
schemaHeaders?: List<SchemaHeaderField>; // headers for schema view
clipWidth?: number; // percent transition from before to after in comparisonBox
dockingConfig?: string;
annotationOn?: Doc;
isPushpin?: boolean;
+ isGroup?: boolean; // whether a collection should use a grouping UI behavior
_removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
-
+ noteType?: string;
// BACKGROUND GRID
_backgroundGridShow?: boolean;
@@ -239,6 +289,7 @@ export class DocumentOptions {
numBtnMax?: number;
numBtnMin?: number;
switchToggle?: boolean;
+ badgeValue?: ScriptField;
//LINEAR VIEW
linearViewIsExpanded?: boolean; // is linear view expanded
@@ -247,7 +298,7 @@ export class DocumentOptions {
linearViewSubMenu?: boolean;
linearViewFloating?: boolean;
flexGap?: number; // Linear view flex gap
- flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse";
+ flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse';
layout_linkView?: Doc; // view template for a link document
layout_keyValue?: string; // view tempalte for key value docs
@@ -255,8 +306,8 @@ export class DocumentOptions {
linkDisplay?: boolean; // whether a link line should be dipslayed between the two link anchors
anchor1?: Doc;
anchor2?: Doc;
- "anchor1-useLinkSmallAnchor"?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection)
- "anchor2-useLinkSmallAnchor"?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection)
+ 'anchor1-useLinkSmallAnchor'?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection)
+ 'anchor2-useLinkSmallAnchor'?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection)
ignoreClick?: boolean;
onClick?: ScriptField;
onDoubleClick?: ScriptField;
@@ -269,6 +320,7 @@ export class DocumentOptions {
clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script
onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
cloneFieldFilter?: List<string>; // fields not to copy when the document is clonedclipboard?: Doc;
+ filterBoolean?: string;
useCors?: boolean;
icon?: string;
target?: Doc; // available for use in scripts as the primary target document
@@ -278,11 +330,13 @@ export class DocumentOptions {
strokeWidth?: number;
freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
+ treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)
treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
-
+ treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically
+ treeViewChildDoubleClick?: ScriptField; //
// Action Button
- buttonMenu?: boolean; // whether a action button should be displayed
+ buttonMenu?: boolean; // whether a action button should be displayed
buttonMenuDoc?: Doc;
explainer?: string;
@@ -295,220 +349,315 @@ export class DocumentOptions {
treeViewTruncateTitleWidth?: number;
treeViewHasOverlay?: boolean; // whether the treeview has an overlay for freeform annotations
treeViewType?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy
- sidebarColor?: string; // background color of text sidebar
+ sidebarColor?: string; // background color of text sidebar
sidebarViewType?: string; // collection type of text sidebar
docMaxAutoHeight?: number; // maximum height for newly created (eg, from pasting) text documents
text?: string;
textTransform?: string; // is linear view expanded
letterSpacing?: string; // is linear view expanded
+ iconTemplate?: string; // name of icon template style
selectedIndex?: number; // which item in a linear view has been selected using the "thumb doc" ui
clipboard?: Doc;
searchQuery?: string; // for quersyBox
- useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox
+ useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox
border?: string; //for searchbox
hoverBackgroundColor?: string; // background color of a label when hovered
linkRelationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor)
- linkRelationshipSizes?: List<number>; //stores number of links contained in each relationship
+ linkRelationshipSizes?: List<number>; //stores number of links contained in each relationship
linkColorList?: List<string>; // colors of links corresponding to specific link relationships
}
export namespace Docs {
-
- const _docOptions = new DocumentOptions();
-
- export async function setupFieldInfos() {
- return await DocServer.GetRefField("FieldInfos8") as Doc ??
- runInAction(() => {
- const infos = new Doc("FieldInfos8", true);
- const keys = Object.keys(new DocumentOptions());
- for (const key of keys) {
- const options = (_docOptions as any)[key] as FInfo;
- const finfo = new Doc();
- finfo.name = key;
- switch (options.type) {
- case "boolean": finfo.options = new List<boolean>(options.values as any as boolean[]); break;
- case "number": finfo.options = new List<number>(options.values as any as number[]); break;
- case "Doc": finfo.options = new List<Doc>(options.values as any as Doc[]); break;
- default: // string, pointerEvents, dimUnit, dropActionType
- finfo.options = new List<string>(options.values as any as string[]); break;
- }
- finfo.layoutField = options.layoutField;
- finfo.description = options.description;
- finfo.type = options.type;
- infos[key] = finfo;
- }
- return infos;
- });
- }
-
export let newAccount: boolean = false;
export namespace Prototypes {
-
type LayoutSource = { LayoutString: (key: string) => string };
type PrototypeTemplate = {
layout: {
- view: LayoutSource,
- dataField: string
- },
- data?: any,
- options?: Partial<DocumentOptions>
+ view: LayoutSource;
+ dataField: string;
+ };
+ data?: any;
+ options?: Partial<DocumentOptions>;
};
type TemplateMap = Map<DocumentType, PrototypeTemplate>;
type PrototypeMap = Map<DocumentType, Doc>;
- const defaultDataKey = "data";
+ const defaultDataKey = 'data';
const TemplateMap: TemplateMap = new Map([
- [DocumentType.RTF, {
- layout: { view: FormattedTextBox, dataField: "text" },
- options: {
- _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true,
- links: "@links(self)"
- }
- }],
- [DocumentType.SEARCH, {
- layout: { view: SearchBox, dataField: defaultDataKey },
- options: { _width: 400, links: "@links(self)" }
- }],
- [DocumentType.FILTER, {
- layout: { view: FilterBox, dataField: defaultDataKey },
- options: { _width: 400, links: "@links(self)" }
- }],
- [DocumentType.COLOR, {
- layout: { view: ColorBox, dataField: defaultDataKey },
- options: { _nativeWidth: 220, _nativeHeight: 300, links: "@links(self)" }
- }],
- [DocumentType.IMG, {
- layout: { view: ImageBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.WEB, {
- layout: { view: WebBox, dataField: defaultDataKey },
- options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" }
- }],
- [DocumentType.COL, {
- layout: { view: CollectionView, dataField: defaultDataKey },
- options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: "@links(self)" }
- }],
- [DocumentType.KVP, {
- layout: { view: KeyValueBox, dataField: defaultDataKey },
- options: { _fitWidth: true, _height: 150 }
- }],
- [DocumentType.VID, {
- layout: { view: VideoBox, dataField: defaultDataKey },
- options: { _currentTimecode: 0, links: "@links(self)" },
- }],
- [DocumentType.AUDIO, {
- layout: { view: AudioBox, dataField: defaultDataKey },
- options: { _height: 100, backgroundColor: "lightGray", links: "@links(self)" }
- }],
- [DocumentType.PDF, {
- layout: { view: PDFBox, dataField: defaultDataKey },
- options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" }
- }],
- [DocumentType.MAP, {
- layout: { view: MapBox, dataField: defaultDataKey },
- options: { _height: 600, _width: 800, links: "@links(self)" }
- }],
- [DocumentType.IMPORT, {
- layout: { view: DirectoryImportBox, dataField: defaultDataKey },
- options: { _height: 150 }
- }],
- [DocumentType.LINK, {
- layout: { view: LinkBox, dataField: defaultDataKey },
- options: {
- childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", showCaption: "description",
- backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area
- links: "@links(self)",
- _removeDropProperties: new List(["_layerTags", "isLinkButton"]),
- }
- }],
- [DocumentType.LINKDB, {
- data: new List<Doc>(),
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { childDropAction: "alias", title: "Global Link Database" }
- }],
- [DocumentType.SCRIPTDB, {
- data: new List<Doc>(),
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { childDropAction: "alias", title: "Global Script Database" }
- }],
- [DocumentType.SCRIPTING, {
- layout: { view: ScriptingBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.YOUTUBE, {
- layout: { view: YoutubeBox, dataField: defaultDataKey }
- }],
- [DocumentType.LABEL, {
- layout: { view: LabelBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.EQUATION, {
- layout: { view: EquationBox, dataField: defaultDataKey },
- options: { links: "@links(self)", hideResizeHandles: true, hideDecorationTitle: true }
- }],
- [DocumentType.FUNCPLOT, {
- layout: { view: FunctionPlotBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.BUTTON, {
- layout: { view: LabelBox, dataField: "onClick" },
- options: { links: "@links(self)" }
- }],
- [DocumentType.SLIDER, {
- layout: { view: SliderBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.PRES, {
- layout: { view: PresBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.FONTICON, {
- layout: { view: FontIconBox, dataField: defaultDataKey },
- options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: "@links(self)" },
- }],
- [DocumentType.WEBCAM, {
- layout: { view: DashWebRTCVideo, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.PRESELEMENT, {
- layout: { view: PresElementBox, dataField: defaultDataKey }
- }],
- [DocumentType.MARKER, {
- layout: { view: CollectionView, dataField: defaultDataKey },
- options: { links: "@links(self)", hideLinkButton: true }
- }],
- [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
- layout: { view: InkingStroke, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.SCREENSHOT, {
- layout: { view: ScreenshotBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
- [DocumentType.COMPARISON, {
- layout: { view: ComparisonBox, dataField: defaultDataKey },
- options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" }
- }],
- [DocumentType.GROUPDB, {
- data: new List<Doc>(),
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { childDropAction: "alias", title: "Global Group Database" }
- }],
- [DocumentType.GROUP, {
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
- }],
+ [
+ DocumentType.RTF,
+ {
+ layout: { view: FormattedTextBox, dataField: 'text' },
+ options: {
+ _height: 35,
+ _xMargin: 10,
+ _yMargin: 10,
+ nativeDimModifiable: true,
+ treeViewGrowsHorizontally: true,
+ forceReflow: true,
+ links: '@links(self)',
+ },
+ },
+ ],
+ [
+ DocumentType.SEARCH,
+ {
+ layout: { view: SearchBox, dataField: defaultDataKey },
+ options: { _width: 400, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.FILTER,
+ {
+ layout: { view: FilterBox, dataField: defaultDataKey },
+ options: { _width: 400, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.COLOR,
+ {
+ layout: { view: ColorBox, dataField: defaultDataKey },
+ options: { _nativeWidth: 220, _nativeHeight: 300, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.IMG,
+ {
+ layout: { view: ImageBox, dataField: defaultDataKey },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.WEB,
+ {
+ layout: { view: WebBox, dataField: defaultDataKey },
+ options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.COL,
+ {
+ layout: { view: CollectionView, dataField: defaultDataKey },
+ options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.KVP,
+ {
+ layout: { view: KeyValueBox, dataField: defaultDataKey },
+ options: { _fitWidth: true, _height: 150 },
+ },
+ ],
+ [
+ DocumentType.VID,
+ {
+ layout: { view: VideoBox, dataField: defaultDataKey },
+ options: { _currentTimecode: 0, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.AUDIO,
+ {
+ layout: { view: AudioBox, dataField: defaultDataKey },
+ options: { _height: 100, backgroundColor: 'lightGray', _fitWidth: true, forceReflow: true, nativeDimModifiable: true, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.REC,
+ {
+ layout: { view: VideoBox, dataField: defaultDataKey },
+ options: { _height: 100, backgroundColor: 'pink', links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.PDF,
+ {
+ layout: { view: PDFBox, dataField: defaultDataKey },
+ options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.MAP,
+ {
+ layout: { view: MapBox, dataField: defaultDataKey },
+ options: { _height: 600, _width: 800, nativeDimModifiable: true, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.IMPORT,
+ {
+ layout: { view: DirectoryImportBox, dataField: defaultDataKey },
+ options: { _height: 150 },
+ },
+ ],
+ [
+ DocumentType.LINK,
+ {
+ layout: { view: LinkBox, dataField: defaultDataKey },
+ options: {
+ childDontRegisterViews: true,
+ _isLinkButton: true,
+ _height: 150,
+ description: '',
+ showCaption: 'description',
+ backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
+ links: '@links(self)',
+ _removeDropProperties: new List(['isLinkButton']),
+ },
+ },
+ ],
+ [
+ DocumentType.LINKDB,
+ {
+ data: new List<Doc>(),
+ layout: { view: EmptyBox, dataField: defaultDataKey },
+ options: { childDropAction: 'alias', title: 'Global Link Database' },
+ },
+ ],
+ [
+ DocumentType.SCRIPTDB,
+ {
+ data: new List<Doc>(),
+ layout: { view: EmptyBox, dataField: defaultDataKey },
+ options: { childDropAction: 'alias', title: 'Global Script Database' },
+ },
+ ],
+ [
+ DocumentType.SCRIPTING,
+ {
+ layout: { view: ScriptingBox, dataField: defaultDataKey },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.YOUTUBE,
+ {
+ layout: { view: YoutubeBox, dataField: defaultDataKey },
+ },
+ ],
+ [
+ DocumentType.LABEL,
+ {
+ layout: { view: LabelBox, dataField: defaultDataKey },
+ options: { links: '@links(self)', _singleLine: true },
+ },
+ ],
+ [
+ DocumentType.EQUATION,
+ {
+ layout: { view: EquationBox, dataField: defaultDataKey },
+ options: { links: '@links(self)', nativeDimModifiable: true, hideResizeHandles: true, hideDecorationTitle: true },
+ },
+ ],
+ [
+ DocumentType.FUNCPLOT,
+ {
+ layout: { view: FunctionPlotBox, dataField: defaultDataKey },
+ options: { nativeDimModifiable: true, links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.BUTTON,
+ {
+ layout: { view: LabelBox, dataField: 'onClick' },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.SLIDER,
+ {
+ layout: { view: SliderBox, dataField: defaultDataKey },
+ options: { links: '@links(self)', treeViewGrowsHorizontally: true },
+ },
+ ],
+ [
+ DocumentType.PRES,
+ {
+ layout: { view: PresBox, dataField: defaultDataKey },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.FONTICON,
+ {
+ layout: { view: FontIconBox, dataField: defaultDataKey },
+ options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%', links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.WEBCAM,
+ {
+ layout: { view: RecordingBox, dataField: defaultDataKey },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.PRESELEMENT,
+ {
+ layout: { view: PresElementBox, dataField: defaultDataKey },
+ },
+ ],
+ [
+ DocumentType.MARKER,
+ {
+ layout: { view: CollectionView, dataField: defaultDataKey },
+ options: { links: '@links(self)', hideLinkButton: true, pointerEvents: 'none' },
+ },
+ ],
+ [
+ DocumentType.INK,
+ {
+ // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
+ layout: { view: InkingStroke, dataField: defaultDataKey },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.SCREENSHOT,
+ {
+ layout: { view: ScreenshotBox, dataField: defaultDataKey },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.COMPARISON,
+ {
+ layout: { view: ComparisonBox, dataField: defaultDataKey },
+ options: { clipWidth: 50, nativeDimModifiable: true, backgroundColor: 'gray', targetDropAction: 'alias', links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.GROUPDB,
+ {
+ data: new List<Doc>(),
+ layout: { view: EmptyBox, dataField: defaultDataKey },
+ options: { childDropAction: 'alias', title: 'Global Group Database' },
+ },
+ ],
+ [
+ DocumentType.GROUP,
+ {
+ layout: { view: EmptyBox, dataField: defaultDataKey },
+ options: { links: '@links(self)' },
+ },
+ ],
+ [
+ DocumentType.DATAVIZ,
+ {
+ layout: { view: DataVizBox, dataField: defaultDataKey },
+ options: { _fitWidth: true, nativeDimModifiable: true, links: '@links(self)' },
+ },
+ ],
]);
- const suffix = "Proto";
+ const suffix = 'Proto';
/**
* This function loads or initializes the prototype for each docment type.
- *
+ *
* This is an asynchronous function because it has to attempt
* to fetch the prototype documents from the server.
- *
+ *
* Once we have this object that maps the prototype ids to a potentially
* undefined document, we either initialize our private prototype
* variables with the document returned from the server or, if prototypes
@@ -518,22 +667,15 @@ export namespace Docs {
ProxyField.initPlugin();
ComputedField.initPlugin();
// non-guid string ids for each document prototype
- const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix);
+ const prototypeIds = Object.values(DocumentType)
+ .filter(type => type !== DocumentType.NONE)
+ .map(type => type + suffix);
// fetch the actual prototype documents from the server
const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds);
- if (!Docs.newAccount) {
- Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB
- Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true;
- Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true;
- Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB
- Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true;
- Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true;
- }
-
// update this object to include any default values: DocumentOptions for all prototypes
prototypeIds.map(id => {
const existing = actualProtos[id] as Doc;
- const type = id.replace(suffix, "") as DocumentType;
+ const type = id.replace(suffix, '') as DocumentType;
// get or create prototype of the specified type...
const target = existing || buildPrototype(type, id);
// ...and set it if not undefined (can be undefined only if TemplateMap does not contain
@@ -546,30 +688,38 @@ export namespace Docs {
* Retrieves the prototype for the given document type, or
* undefined if that type's proto doesn't have a configuration
* in the template map.
- * @param type
+ * @param type
*/
const PrototypeMap: PrototypeMap = new Map();
- export function get(type: DocumentType): Doc { return PrototypeMap.get(type)!; }
+ export function get(type: DocumentType): Doc {
+ return PrototypeMap.get(type)!;
+ }
/**
* A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here.
*/
- export function MainLinkDocument() { return Prototypes.get(DocumentType.LINKDB); }
+ export function MainLinkDocument() {
+ return Prototypes.get(DocumentType.LINKDB);
+ }
/**
* A collection of all scripts in the database
*/
- export function MainScriptDocument() { return Prototypes.get(DocumentType.SCRIPTDB); }
+ export function MainScriptDocument() {
+ return Prototypes.get(DocumentType.SCRIPTDB);
+ }
/**
* A collection of all user acl groups in the database
*/
- export function MainGroupDocument() { return Prototypes.get(DocumentType.GROUPDB); }
+ export function MainGroupDocument() {
+ return Prototypes.get(DocumentType.GROUPDB);
+ }
/**
* This is a convenience method that is used to initialize
* prototype documents for the first time.
- *
+ *
* @param protoId the id of the prototype, indicating the specific prototype
* to initialize (see the *protoId list at the top of the namespace)
* @param title the prototype document's title, follows *-PROTO
@@ -591,11 +741,21 @@ export namespace Docs {
// synthesize the default options, the type and title from computed values and
// whatever options pertain to this specific prototype
const options: DocumentOptions = {
- system: true, _layoutKey: "layout", title, type, baseProto: true, x: 0, y: 0, _width: 300, ...(template.options || {}),
- layout: layout.view?.LayoutString(layout.dataField), data: template.data, layout_keyValue: KeyValueBox.LayoutString("")
+ system: true,
+ _layoutKey: 'layout',
+ title,
+ type,
+ baseProto: true,
+ x: 0,
+ y: 0,
+ _width: 300,
+ ...(template.options || {}),
+ layout: layout.view?.LayoutString(layout.dataField),
+ data: template.data,
+ layout_keyValue: KeyValueBox.LayoutString(''),
};
Object.entries(options).map(pair => {
- if (typeof pair[1] === "string" && pair[1].startsWith("@")) {
+ if (typeof pair[1] === 'string' && pair[1].startsWith('@')) {
(options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1));
}
});
@@ -608,16 +768,15 @@ export namespace Docs {
* delegated from top-level prototypes
*/
export namespace Create {
-
/**
* This function receives the relevant document prototype and uses
* it to create a new of that base-level prototype, or the
- * underlying data document, which it then delegates again
+ * underlying data document, which it then delegates again
* to create the view document.
- *
+ *
* It also takes the opportunity to register the user
* that created the document and the time of creation.
- *
+ *
* @param proto the specific document prototype off of which to model
* this new instance (textProto, imageProto, etc.)
* @param data the Field to store at this new instance's data key
@@ -627,51 +786,57 @@ export namespace Docs {
* only when creating a DockDocument from the current user's already existing
* main document.
*/
- function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) {
- const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_"
- const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_");
+ function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string) {
+ const viewKeys = ['x', 'y', 'system']; // keys that should be addded to the view document even though they don't begin with an "_"
+ const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_');
+
+ dataProps['acl-Override'] = 'None';
+ dataProps['acl-Public'] = options['acl-Public'] ? options['acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
dataProps.system = viewProps.system;
dataProps.isPrototype = true;
dataProps.author = Doc.CurrentUserEmail;
- dataProps.creationDate = new DateField;
- dataProps[`${fieldKey}-lastModified`] = new DateField;
- dataProps["acl-Override"] = "None";
- dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+ dataProps.creationDate = new DateField();
+ dataProps[`${fieldKey}-lastModified`] = new DateField();
dataProps[fieldKey] = data;
// so that the list of annotations is already initialised, prevents issues in addonly.
// without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do.
- dataProps[fieldKey + "-annotations"] = new List<Doc>();
+ dataProps[fieldKey + '-annotations'] = new List<Doc>();
+ dataProps[fieldKey + '-sidebar'] = new List<Doc>();
const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true);
- viewProps.author = Doc.CurrentUserEmail;
- viewProps["acl-Override"] = "None";
- viewProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
- const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true);
+ const viewFirstProps: { [id: string]: any } = {};
+ viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+ viewFirstProps['acl-Override'] = 'None';
+ viewFirstProps.author = Doc.CurrentUserEmail;
+ const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true);
+ Doc.assign(viewDoc, viewProps, true, true);
![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
- !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) &&
- !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc);
+ !Doc.IsSystem(dataDoc) &&
+ ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) &&
+ !dataDoc.isFolder &&
+ !dataProps.annotationOn &&
+ Doc.AddDocToList(Doc.MyFileOrphans, undefined, dataDoc);
updateCachedAcls(dataDoc);
updateCachedAcls(viewDoc);
return viewDoc;
}
- export function ImageDocument(url: string, options: DocumentOptions = {}) {
- const imgField = new ImageField(url);
- return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options });
+ export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}) {
+ const imgField = url instanceof ImageField ? url : new ImageField(url);
+ return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options });
}
- export function PresDocument(initial: List<Doc> = new List(), options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options);
+ export function PresDocument(options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List<Doc>(), options);
}
- export function ScriptingDocument(script: Opt<ScriptField>, options: DocumentOptions = {}, fieldKey?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script,
- { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
+ export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
}
export function VideoDocument(url: string, options: DocumentOptions = {}) {
@@ -683,20 +848,23 @@ export namespace Docs {
}
export function WebCamDocument(url: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options);
+ return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), '', options);
}
- export function ScreenshotDocument(title: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", { ...options, title });
+ export function ScreenshotDocument(options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), '', options);
}
- export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) {
- return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", options);
+ export function ComparisonDocument(options: DocumentOptions = { title: 'Comparison Box' }) {
+ return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), '', options);
}
export function AudioDocument(url: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url),
- { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any });
+ return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any });
+ }
+
+ export function RecordingDocument(url: string, options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.REC), '', options);
}
export function SearchDocument(options: DocumentOptions = {}) {
@@ -704,48 +872,60 @@ export namespace Docs {
}
export function ColorDocument(options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.COLOR), "", options);
+ return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options);
}
- export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = "text") {
+ export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
}
- export function TextDocument(text: string, options: DocumentOptions = {}, fieldKey: string = "text") {
+ export function TextDocument(text: string, options: DocumentOptions = {}, fieldKey: string = 'text') {
const rtf = {
doc: {
- type: "doc", content: [{
- type: "paragraph",
- content: [{
- type: "text",
- text
- }]
- }]
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text,
+ },
+ ],
+ },
+ ],
},
- selection: { type: "text", anchor: 1, head: 1 },
- storedMarks: []
+ selection: { type: 'text', anchor: 1, head: 1 },
+ storedMarks: [],
};
const field = text ? new RichTextField(JSON.stringify(rtf), text) : undefined;
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
}
- export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
- const linkDoc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, {
- anchor1: source.doc, anchor2: target.doc, ...options
- }, id);
+ export function LinkDocument(source: { doc: Doc; ctx?: Doc }, target: { doc: Doc; ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
+ const linkDoc = InstanceFromProto(
+ Prototypes.get(DocumentType.LINK),
+ undefined,
+ {
+ anchor1: source.doc,
+ anchor2: target.doc,
+ ...options,
+ },
+ id
+ );
LinkManager.Instance.addLink(linkDoc);
return linkDoc;
}
- export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
+ export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) {
const I = new Doc();
I[Initializing] = true;
I.type = DocumentType.INK;
- I.layout = InkingStroke.LayoutString("data");
+ I.layout = InkingStroke.LayoutString('data');
I.color = color;
- I.hideDecorationTitle = true; // don't show title when selected
- I.hideOpenButton = true; // don't show open full screen button when selected
+ I.hideDecorationTitle = true; // don't show title when selected
+ // I.hideOpenButton = true; // don't show open full screen button when selected
I.fillColor = fillColor;
I.strokeWidth = strokeWidth;
I.strokeBezier = strokeBezier;
@@ -753,19 +933,20 @@ export namespace Docs {
I.strokeEndMarker = arrowEnd;
I.strokeDash = dash;
I.tool = tool;
- I["text-align"] = "center";
- I.title = "ink";
- I.x = options.x;
- I.y = options.y;
+ I['text-align'] = 'center';
+ I.title = 'ink';
+ I.x = options.x as number;
+ I.y = options.y as number;
I._width = options._width as number;
I._height = options._height as number;
- I._fontFamily = "cursive";
+ I._fontFamily = 'cursive';
I.author = Doc.CurrentUserEmail;
I.rotation = 0;
I.data = new InkField(points);
- I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
- I["acl-Override"] = "None";
- I.links = "@links(self)";
+ I.creationDate = new DateField();
+ I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+ I['acl-Override'] = 'None';
+ I.links = ComputedField.MakeFunction('links(self)');
I[Initializing] = false;
return I;
}
@@ -775,7 +956,7 @@ export namespace Docs {
const height = options._height || undefined;
const nwid = options._nativeWidth || undefined;
const nhght = options._nativeHeight || undefined;
- if (!nhght && width && height && nwid) options._nativeHeight = Number(nwid) * Number(height) / Number(width);
+ if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width);
return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options);
}
@@ -784,8 +965,8 @@ export namespace Docs {
const height = options._height || undefined;
const nwid = options._nativeWidth || undefined;
const nhght = options._nativeHeight || undefined;
- if (!nhght && width && height && nwid) options._nativeHeight = Number(nwid) * Number(height) / Number(width);
- return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(url) : undefined, options);
+ if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width);
+ return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'http://www.bing.com/'), options);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -801,12 +982,12 @@ export namespace Docs {
}
export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options });
+ return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options });
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Freeform }, id);
- documents.map(d => d.context = inst);
+ const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id);
+ documents.map(d => (d.context = inst));
return inst;
}
@@ -823,7 +1004,7 @@ export namespace Docs {
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
}
export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -855,8 +1036,15 @@ export namespace Docs {
}
export function NoteTakingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.NoteTaking }, id, undefined, protoId);
- }
+ return InstanceFromProto(
+ Prototypes.get(DocumentType.COL),
+ new List(documents),
+ { columnHeaders: new List<SchemaHeaderField>([new SchemaHeaderField('Untitled')]), ...options, _viewType: CollectionViewType.NoteTaking },
+ id,
+ undefined,
+ protoId
+ );
+ }
export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Multicolumn });
@@ -882,7 +1070,7 @@ export namespace Docs {
}
export function ButtonDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), "onClick-rawScript": "-script-" });
+ return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) });
}
export function SliderDocument(options?: DocumentOptions) {
@@ -900,10 +1088,12 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) });
}
+ export function DataVizDocument(url: string, options?: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options });
+ }
+
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
- const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true });
- const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true });
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
}
export function DirectoryImportDocument(options: DocumentOptions = {}) {
@@ -911,23 +1101,26 @@ export namespace Docs {
}
export type DocConfig = {
- doc: Doc,
- initialWidth?: number,
- path?: Doc[]
+ doc: Doc;
+ initialWidth?: number;
+ path?: Doc[];
};
- export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = "row") {
+ export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = 'row') {
const layoutConfig = {
content: [
{
type: type,
- content: [
- ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))
- ]
- }
- ]
+ content: [...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))],
+ },
+ ],
};
- return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id);
+ return DockDocument(
+ configs.map(c => c.doc),
+ JSON.stringify(layoutConfig),
+ options,
+ id
+ );
}
export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) {
@@ -938,9 +1131,9 @@ export namespace Docs {
export namespace DocUtils {
export function Excluded(d: Doc, docFilters: string[]) {
- const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
+ const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
docFilters.forEach(filter => {
- const fields = filter.split(":");
+ const fields = filter.split(':');
const key = fields[0];
const value = fields[1];
const modifiers = fields[2];
@@ -953,7 +1146,7 @@ export namespace DocUtils {
if (d.z) return false;
for (const facetKey of Object.keys(filterFacets)) {
const facet = filterFacets[facetKey];
- const xs = Object.keys(facet).filter(value => facet[value] === "x");
+ const xs = Object.keys(facet).filter(value => facet[value] === 'x');
const failsNotEqualFacets = xs?.some(value => Doc.matchFieldValue(d, facetKey, value));
if (failsNotEqualFacets) {
return true;
@@ -962,21 +1155,21 @@ export namespace DocUtils {
return false;
}
/**
- * @param docs
- * @param docFilters
- * @param docRangeFilters
- * @param viewSpecScript
- * Given a list of docs and docFilters, @returns the list of Docs that match those filters
+ * @param docs
+ * @param docFilters
+ * @param docRangeFilters
+ * @param viewSpecScript
+ * Given a list of docs and docFilters, @returns the list of Docs that match those filters
*/
export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField, parentCollection?: Doc) {
const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
if (!docFilters?.length && !docRangeFilters?.length) {
- return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
+ return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
}
- const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
+ const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
docFilters.forEach(filter => {
- const fields = filter.split(":");
+ const fields = filter.split(':');
const key = fields[0];
const value = fields[1];
const modifiers = fields[2];
@@ -986,67 +1179,70 @@ export namespace DocUtils {
filterFacets[key][value] = modifiers;
});
- const filteredDocs = docFilters.length ? childDocs.filter(d => {
- if (d.z) return true;
- // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter
- if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
- return false;
- }
-
- for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== "cookies")) {
- const facet = filterFacets[facetKey];
-
- // facets that match some value in the field of the document (e.g. some text field)
- const matches = Object.keys(facet).filter(value => value !== "cookies" && facet[value] === "match");
-
- // facets that have a check next to them
- const checks = Object.keys(facet).filter(value => facet[value] === "check");
-
- // metadata facets that exist
- const exists = Object.keys(facet).filter(value => facet[value] === "exists");
-
- // metadata facets that exist
- const unsets = Object.keys(facet).filter(value => facet[value] === "unset");
-
- // facets that have an x next to them
- const xs = Object.keys(facet).filter(value => facet[value] === "x");
-
- if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
- const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
- const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
- const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined);
- const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
- const satisfiesMatchFacets = !matches.length ? true : matches.some(value => {
- if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc
- const allKeys = Array.from(Object.keys(d));
- allKeys.push(...Object.keys(Doc.GetProto(d)));
- const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
- return keys.some(key => Field.toString(d[key] as Field).includes(value));
- }
- return Field.toString(d[facetKey] as Field).includes(value);
- });
- // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
- if ((parentCollection?.currentFilter as Doc)?.filterBoolean === "OR") {
- if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
- }
- // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
- else {
- if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
- }
-
- }
- return (parentCollection?.currentFilter as Doc)?.filterBoolean === "OR" ? false : true;
- }) : childDocs;
+ const filteredDocs = docFilters.length
+ ? childDocs.filter(d => {
+ if (d.z) return true;
+ // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter
+ if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
+ return false;
+ }
+
+ for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies')) {
+ const facet = filterFacets[facetKey];
+
+ // facets that match some value in the field of the document (e.g. some text field)
+ const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match');
+
+ // facets that have a check next to them
+ const checks = Object.keys(facet).filter(value => facet[value] === 'check');
+
+ // metadata facets that exist
+ const exists = Object.keys(facet).filter(value => facet[value] === 'exists');
+
+ // metadata facets that exist
+ const unsets = Object.keys(facet).filter(value => facet[value] === 'unset');
+
+ // facets that have an x next to them
+ const xs = Object.keys(facet).filter(value => facet[value] === 'x');
+
+ if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
+ const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
+ const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
+ const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined);
+ const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
+ const satisfiesMatchFacets = !matches.length
+ ? true
+ : matches.some(value => {
+ if (facetKey.startsWith('*')) {
+ // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc
+ const allKeys = Array.from(Object.keys(d));
+ allKeys.push(...Object.keys(Doc.GetProto(d)));
+ const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
+ return keys.some(key => Field.toString(d[key] as Field).includes(value));
+ }
+ return Field.toString(d[facetKey] as Field).includes(value);
+ });
+ // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
+ if ((parentCollection?.currentFilter as Doc)?.filterBoolean === 'OR') {
+ if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
+ }
+ // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
+ else {
+ if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
+ }
+ }
+ return (parentCollection?.currentFilter as Doc)?.filterBoolean === 'OR' ? false : true;
+ })
+ : childDocs;
const rangeFilteredDocs = filteredDocs.filter(d => {
for (let i = 0; i < docRangeFilters.length; i += 3) {
const key = docRangeFilters[i];
const min = Number(docRangeFilters[i + 1]);
const max = Number(docRangeFilters[i + 2]);
- const val = Cast(d[key], "number", null);
- if (val < min || val > max) return false;
+ const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
if (val === undefined) {
//console.log("Should 'undefined' pass range filter or not?")
- }
+ } else if (val < min || val > max) return false;
}
return true;
});
@@ -1054,7 +1250,7 @@ export namespace DocUtils {
}
export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) {
- targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, "");
+ targetID = targetID.replace(/^-/, '').replace(/\([0-9]*\)$/, '');
DocServer.GetRefField(targetID).then(doc => {
if (promoteDoc !== doc) {
let copy = doc as Doc;
@@ -1068,16 +1264,17 @@ export namespace DocUtils {
remDoc && remDoc(promoteDoc);
if (!doc) {
DocListCastAsync(promoteDoc.links).then(links => {
- links && links.map(async link => {
- if (link) {
- const a1 = await Cast(link.anchor1, Doc);
- if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy;
- const a2 = await Cast(link.anchor2, Doc);
- if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy;
- LinkManager.Instance.deleteLink(link);
- LinkManager.Instance.addLink(link);
- }
- });
+ links &&
+ links.map(async link => {
+ if (link) {
+ const a1 = await Cast(link.anchor1, Doc);
+ if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy;
+ const a2 = await Cast(link.anchor2, Doc);
+ if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy;
+ LinkManager.Instance.deleteLink(link);
+ LinkManager.Instance.addLink(link);
+ }
+ });
});
}
}
@@ -1088,15 +1285,19 @@ export namespace DocUtils {
options?.afterFocus?.(false);
}
- export let ActiveRecordings: { props: FieldViewProps, getAnchor: () => Doc }[] = [];
+ export let ActiveRecordings: { props: FieldViewProps; getAnchor: () => Doc }[] = [];
export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) {
- broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1);
- return DocUtils.ActiveRecordings.map(audio =>
- DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording link", "recording timeline"));
+ broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1));
+ return DocUtils.ActiveRecordings.map(audio => {
+ const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
+ link && (link.followLinkLocation = 'add:right');
+ return link;
+ });
}
- export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
+ export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = '', description: string = '', id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
+ if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
const sv = DocumentManager.Instance.getDocumentView(source.doc);
if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return;
if (target.doc === Doc.UserDoc()) return undefined;
@@ -1105,7 +1306,7 @@ export namespace DocUtils {
if (showPopup) {
LinkManager.currentLink = linkDoc;
- TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.textDisplayed = 'Link Created';
TaskCompletionBox.popupX = showPopup[0];
TaskCompletionBox.popupY = showPopup[1] - 33;
TaskCompletionBox.taskCompleted = true;
@@ -1124,25 +1325,79 @@ export namespace DocUtils {
TaskCompletionBox.popupY -= 40;
}
- setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2500
+ );
}
return linkDoc;
});
- return makeLink(Docs.Create.LinkDocument(source, target, {
- title: ComputedField.MakeFunction("generateLinkTitle(self)") as any,
- "anchor1-useLinkSmallAnchor": source.doc.useLinkSmallAnchor ? true : undefined,
- "anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined,
- "acl-Public": SharingPermissions.Augment,
- "_acl-Public": SharingPermissions.Augment,
- linkDisplay: true,
- _hidden: true,
- _linkAutoMove: true,
- linkRelationship,
- _showCaption: "description",
- _showTitle: "linkRelationship",
- description
- }, id), showPopup);
+ return makeLink(
+ Docs.Create.LinkDocument(
+ source,
+ target,
+ {
+ title: ComputedField.MakeFunction('generateLinkTitle(self)') as any,
+ 'anchor1-useLinkSmallAnchor': source.doc.useLinkSmallAnchor ? true : undefined,
+ 'anchor2-useLinkSmallAnchor': target.doc.useLinkSmallAnchor ? true : undefined,
+ 'acl-Public': SharingPermissions.Augment,
+ '_acl-Public': SharingPermissions.Augment,
+ linkDisplay: true,
+ _hidden: true,
+ _linkAutoMove: true,
+ linkRelationship,
+ _showCaption: 'description',
+ _showTitle: 'linkRelationship',
+ description,
+ },
+ id
+ ),
+ showPopup
+ );
+ }
+
+ export function AssignScripts(doc: Doc, scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
+ scripts &&
+ Object.keys(scripts).map(key => {
+ if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) {
+ doc[key] = ScriptField.MakeScript(
+ scripts[key],
+ { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name },
+ { _readOnly_: true }
+ );
+ }
+ });
+ funcs &&
+ Object.keys(funcs).map(key => {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) {
+ doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true });
+ }
+ });
+ return doc;
+ }
+ export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) {
+ if (doc) {
+ const compareValues = (val1: any, val2: any) => {
+ if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) {
+ return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v));
+ }
+ return val1 === val2;
+ };
+ Object.entries(reqdOpts).forEach(pair => {
+ const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc);
+ if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) {
+ targetDoc[pair[0]] = pair[1];
+ }
+ });
+ items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
+ items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
+ }
+ return doc;
+ }
+ export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
}
export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
@@ -1151,21 +1406,24 @@ export namespace DocUtils {
const field = target[fieldKey];
const resolved = options || {};
if (field instanceof ImageField) {
- created = Docs.Create.ImageDocument((field).url.href, resolved);
+ created = Docs.Create.ImageDocument(field.url.href, resolved);
layout = ImageBox.LayoutString;
} else if (field instanceof Doc) {
created = field;
} else if (field instanceof VideoField) {
- created = Docs.Create.VideoDocument((field).url.href, resolved);
+ created = Docs.Create.VideoDocument(field.url.href, resolved);
layout = VideoBox.LayoutString;
} else if (field instanceof PdfField) {
- created = Docs.Create.PdfDocument((field).url.href, resolved);
+ created = Docs.Create.PdfDocument(field.url.href, resolved);
layout = PDFBox.LayoutString;
} else if (field instanceof AudioField) {
- created = Docs.Create.AudioDocument((field).url.href, resolved);
+ created = Docs.Create.AudioDocument(field.url.href, resolved);
layout = AudioBox.LayoutString;
+ } else if (field instanceof RecordingField) {
+ created = Docs.Create.RecordingDocument(field.url.href, resolved);
+ layout = RecordingBox.LayoutString;
} else if (field instanceof InkField) {
- created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved);
+ created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, resolved);
layout = InkingStroke.LayoutString;
} else if (field instanceof List && field[0] instanceof Doc) {
created = Docs.Create.StackingDocument(DocListCast(field), resolved);
@@ -1173,9 +1431,8 @@ export namespace DocUtils {
} else if (field instanceof MapField) {
created = Docs.Create.MapDocument(DocListCast(field), resolved);
layout = MapBox.LayoutString;
- }
- else {
- created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
+ } else {
+ created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
layout = FormattedTextBox.LayoutString;
}
if (created) {
@@ -1187,23 +1444,28 @@ export namespace DocUtils {
}
export async function DocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
- let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
- if (type.indexOf("image") !== -1) {
+ let ctor: ((path: string, options: DocumentOptions) => Doc | Promise<Doc | undefined>) | undefined = undefined;
+ if (type.indexOf('image') !== -1) {
ctor = Docs.Create.ImageDocument;
if (!options._width) options._width = 300;
}
- if (type.indexOf("video") !== -1) {
+ if (type.indexOf('video') !== -1) {
ctor = Docs.Create.VideoDocument;
if (!options._width) options._width = 600;
- if (!options._height) options._height = (options._width as number) * 2 / 3;
+ if (!options._height) options._height = ((options._width as number) * 2) / 3;
}
- if (type.indexOf("audio") !== -1) {
+ if (type.indexOf('audio') !== -1) {
ctor = Docs.Create.AudioDocument;
}
- if (type.indexOf("pdf") !== -1) {
+ if (type.indexOf('pdf') !== -1) {
ctor = Docs.Create.PdfDocument;
if (!options._width) options._width = 400;
- if (!options._height) options._height = (options._width as number) * 1200 / 927;
+ if (!options._height) options._height = ((options._width as number) * 1200) / 927;
+ }
+ if (type.indexOf('csv') !== -1) {
+ ctor = Docs.Create.DataVizDocument;
+ if (!options._width) options._width = 400;
+ if (!options._height) options._height = ((options._width as number) * 1200) / 927;
}
//TODO:al+glr
// if (type.indexOf("map") !== -1) {
@@ -1211,15 +1473,15 @@ export namespace DocUtils {
// if (!options._width) options._width = 800;
// if (!options._height) options._height = (options._width as number) * 3 / 4;
// }
- if (type.indexOf("html") !== -1) {
+ if (type.indexOf('html') !== -1) {
if (path.includes(window.location.hostname)) {
const s = path.split('/');
const id = s[s.length - 1];
return DocServer.GetRefField(id).then(field => {
if (field instanceof Doc) {
const alias = Doc.MakeAlias(field);
- alias.x = options.x || 0;
- alias.y = options.y || 0;
+ alias.x = (options.x as number) || 0;
+ alias.y = (options.y as number) || 0;
alias._width = (options._width as number) || 300;
alias._height = (options._height as number) || (options._width as number) || 300;
return alias;
@@ -1228,122 +1490,122 @@ export namespace DocUtils {
});
}
ctor = Docs.Create.WebDocument;
- options = { ...options, _width: 400, _height: 512, title: path, };
+ options = { ...options, _width: 400, _height: 512, title: path };
}
+
return ctor ? ctor(path, options) : undefined;
}
export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
- !simpleMenu && ContextMenu.Instance.addItem({
- description: "Quick Notes",
- subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({
- description: ":" + StrCast(note.title),
- event: undoBatch((args: { x: number, y: number }) => {
- const textDoc = Docs.Create.TextDocument("", {
- _width: 200, x, y, _autoHeight: note._autoHeight !== false,
- title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1),
- });
- textDoc.layoutKey = "layout_" + note.title;
- textDoc[textDoc.layoutKey] = note;
- if (pivotField) {
- textDoc[pivotField] = pivotValue
+ !simpleMenu &&
+ ContextMenu.Instance.addItem({
+ description: 'Quick Notes',
+ subitems: DocListCast((Doc.UserDoc()['template-notes'] as Doc).data).map((note, i) => ({
+ description: ':' + StrCast(note.title),
+ event: undoBatch((args: { x: number; y: number }) => {
+ const textDoc = Docs.Create.TextDocument('', {
+ _width: 200,
+ x,
+ y,
+ _autoHeight: note._autoHeight !== false,
+ title: StrCast(note.title) + '#' + (note.aliasCount = NumCast(note.aliasCount) + 1),
+ });
+ textDoc.layoutKey = 'layout_' + note.title;
+ textDoc[textDoc.layoutKey] = note;
+ if (pivotField) {
+ textDoc[pivotField] = pivotValue;
+ }
+ docTextAdder(textDoc);
+ }),
+ icon: StrCast(note.icon) as IconProp,
+ })) as ContextMenuProps[],
+ icon: 'sticky-note',
+ });
+ const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+ .filter(btnDoc => !btnDoc.hidden)
+ .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+ .filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation)
+ .map((dragDoc, i) => ({
+ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+ event: undoBatch((args: { x: number; y: number }) => {
+ const newDoc = DocUtils.copyDragFactory(dragDoc);
+ if (newDoc) {
+ newDoc.author = Doc.CurrentUserEmail;
+ newDoc.x = x;
+ newDoc.y = y;
+ EquationBox.SelectOnLoad = newDoc[Id];
+ if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
+ if (pivotField) {
+ newDoc[pivotField] = pivotValue;
+ }
+ docAdder?.(newDoc);
}
- docTextAdder(textDoc);
}),
- icon: StrCast(note.icon) as IconProp
- })) as ContextMenuProps[],
- icon: "sticky-note"
- });
- const math: ContextMenuProps = ({
- description: ":Math", event: () => {
- const created = Docs.Create.EquationDocument();
- if (created) {
- created.author = Doc.CurrentUserEmail;
- created.x = x;
- created.y = y;
- created.width = 300;
- created.height = 35;
- EquationBox.SelectOnLoad = created[Id];
- if (pivotField) {
- created[pivotField] = pivotValue
- }
- docAdder?.(created);
- }
- }, icon: "calculator"
- });
- const documentList: ContextMenuProps[] = DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({
- description: ":" + StrCast(dragDoc.title),
- event: undoBatch((args: { x: number, y: number }) => {
- const newDoc = Doc.copyDragFactory(dragDoc);
- if (newDoc) {
- newDoc.author = Doc.CurrentUserEmail;
- newDoc.x = x;
- newDoc.y = y;
- if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
- if (pivotField) {
- newDoc[pivotField] = pivotValue
- }
- docAdder?.(newDoc);
- }
- }),
- icon: Doc.toIcon(dragDoc),
- })) as ContextMenuProps[];
- documentList.push(math);
+ icon: Doc.toIcon(dragDoc),
+ })) as ContextMenuProps[];
ContextMenu.Instance.addItem({
- description: "Create document",
+ description: 'Create document',
subitems: documentList,
- icon: "file"
+ icon: 'file',
});
- }// applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
- export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
- const batch = UndoManager.StartBatch("makeCustomViewClicked");
+ } // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
+ export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
+ const batch = UndoManager.StartBatch('makeCustomViewClicked');
runInAction(() => {
- doc.layoutKey = "layout_" + templateSignature;
- if (doc[doc.layoutKey] === undefined) {
- createCustomView(doc, creator, templateSignature, docLayoutTemplate);
- }
+ doc.layoutKey = 'layout_' + templateSignature;
+ createCustomView(doc, creator, templateSignature, docLayoutTemplate);
});
batch.end();
return doc;
}
export function findTemplate(templateName: string, type: string, signature: string) {
let docLayoutTemplate: Opt<Doc>;
- const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
- const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
- const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
+ const iconViews = DocListCast(Cast(Doc.UserDoc()['template-icons'], Doc, null)?.data);
+ const templBtns = DocListCast(Cast(Doc.UserDoc()['template-buttons'], Doc, null)?.data);
+ const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data);
const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
- const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
+ const allTemplates = iconViews
+ .concat(templBtns)
+ .concat(noteTypes)
+ .concat(clickFuncs)
+ .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
+ .filter(doc => doc.isTemplateDoc);
// bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
// first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc));
+ !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc));
!docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
return docLayoutTemplate;
}
- export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
- const templateName = templateSignature.replace(/\(.*\)/, "");
- docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature);
+ export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
+ const templateName = templateSignature.replace(/\(.*\)/, '');
+ docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc._isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
- const customName = "layout_" + templateSignature;
+ const customName = 'layout_' + templateSignature;
const _width = NumCast(doc._width);
const _height = NumCast(doc._height);
- const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
-
- let fieldTemplate: Opt<Doc>;
- if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
- fieldTemplate = Docs.Create.TextDocument("", options);
- } else if (doc.data instanceof PdfField) {
- fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
- } else if (doc.data instanceof VideoField) {
- fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof AudioField) {
- fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof ImageField) {
- fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
- }
- const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
-
- fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
- docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _showSidebar: false };
+
+ if (docLayoutTemplate) {
+ if (docLayoutTemplate !== doc[customName]) {
+ Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
+ }
+ } else {
+ let fieldTemplate: Opt<Doc>;
+ if (doc.data instanceof RichTextField || typeof doc.data === 'string') {
+ fieldTemplate = Docs.Create.TextDocument('', options);
+ } else if (doc.data instanceof PdfField) {
+ fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options);
+ } else if (doc.data instanceof VideoField) {
+ fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
+ } else if (doc.data instanceof AudioField) {
+ fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
+ } else if (doc.data instanceof ImageField) {
+ fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
+ }
+ const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+ docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ }
}
export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
Doc.setNativeView(doc);
@@ -1352,81 +1614,76 @@ export namespace DocUtils {
}
}
export function iconify(doc: Doc) {
- const layoutKey = Cast(doc.layoutKey, "string", null);
- DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined);
- if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
+ const layoutKey = Cast(doc.layoutKey, 'string', null);
+ DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined);
+ if (layoutKey && layoutKey !== 'layout' && layoutKey !== 'layout_icon') doc.deiconifyLayout = layoutKey.replace('layout_', '');
}
- export function pileup(docList: Doc[], x?: number, y?: number) {
- let w = 0, h = 0;
+ export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
+ let w = 0,
+ h = 0;
runInAction(() => {
docList.forEach(d => {
DocUtils.iconify(d);
- w = Math.max(d[WidthSym](), w);
- h = Math.max(d[HeightSym](), h);
+ w = Math.max(NumCast(d._width), w);
+ h = Math.max(NumCast(d._height), h);
});
- h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable
docList.forEach((d, i) => {
- d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
- d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
- d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size;
+ d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size;
+ d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ });
+ const aggBounds = aggregateBounds(
+ docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })),
+ 0,
+ 0
+ );
+ docList.forEach((d, i) => {
+ d.x = NumCast(d.x) - (aggBounds.r + aggBounds.x) / 2;
+ d.y = NumCast(d.y) - (aggBounds.b + aggBounds.y) / 2;
+ d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
});
});
- const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, _overflow: "visible" });
- newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
- newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
- newCollection._width = newCollection._height = 110;
- newCollection._jitterRotation = 10;
- return newCollection;
+ if (create) {
+ const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2 });
+ newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
+ newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
+ newCollection._width = newCollection._height = size * 2;
+ newCollection._jitterRotation = 10;
+ return newCollection;
+ }
}
export function LeavePushpin(doc: Doc, annotationField: string) {
if (doc.isPushpin) return undefined;
const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
- const hasContextAnchor = DocListCast(doc.links).
- some(l =>
- (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) ||
- (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
+ const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
const pushpin = Docs.Create.FontIconDocument({
- title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true,
- icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#ACCEF7",
- _width: 15, _height: 15, _xPadding: 0, _isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null)
+ title: 'pushpin',
+ label: '',
+ annotationOn: Cast(doc.annotationOn, Doc, null),
+ isPushpin: true,
+ icon: 'map-pin',
+ x: Cast(doc.x, 'number', null),
+ y: Cast(doc.y, 'number', null),
+ backgroundColor: '#ACCEF7',
+ _width: 15,
+ _height: 15,
+ _xPadding: 0,
+ _isLinkButton: true,
+ _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
});
Doc.AddDocToList(context, annotationField, pushpin);
- const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
+ const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, 'pushpin', '');
doc._timecodeToShow = undefined;
return pushpin;
}
return undefined;
}
- export async function addFieldEnumerations(doc: Opt<Doc>, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) {
- let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey);
- if (!(optionsCollection instanceof Doc)) {
- optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set`, system: true }, enumeratedFieldKey);
- Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc);
- }
- const options = optionsCollection as Doc;
- const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
- const docFind = `options.data?.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
- targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options }));
- targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options }));
- targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options }));
- enumerations.map(enumeration => {
- const found = DocListCast(options.data).find(d => d.title === enumeration.title);
- if (found) {
- found._backgroundColor = enumeration._backgroundColor || found._backgroundColor;
- found._color = enumeration.color || found._color;
- } else {
- Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, { ...enumeration, system: true }));
- }
- });
- return optionsCollection;
- }
-
// /**
- // *
+ // *
// * @param dms Degree Minute Second format exif gps data
// * @param ref ref that determines negativity of decimal coordinates
// * @returns a decimal format of gps latitude / longitude
@@ -1446,7 +1703,7 @@ export namespace DocUtils {
function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) {
var dd = degrees + minutes / 60 + seconds / (60 * 60);
- if (direction === "S" || direction === "W") {
+ if (direction === 'S' || direction === 'W') {
dd = dd * -1;
} // Don't do anything for N or E
return dd;
@@ -1463,15 +1720,18 @@ export namespace DocUtils {
if (doc) {
const proto = Doc.GetProto(doc);
proto.text = result.rawText;
- proto.fileUpload = pathname.replace(/.*\//, "").replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+ proto.fileUpload = pathname
+ .replace(/.*\//, '')
+ .replace('upload_', '')
+ .replace(/\.[a-z0-9]*$/, '');
if (Upload.isImageInformation(result)) {
const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
- proto["data-nativeOrientation"] = result.exifData?.data?.image?.Orientation ?? ((StrCast((result.exifData?.data as any)?.Orientation).includes("Rotate 90")) ? 5 : undefined);
- proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim;
- proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- if (NumCast(proto["data-nativeOrientation"]) >= 5) {
- proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim;
- proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (StrCast((result.exifData?.data as any)?.Orientation).includes('Rotate 90') ? 5 : undefined);
+ proto['data-nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto['data-nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ if (NumCast(proto['data-nativeOrientation']) >= 5) {
+ proto['data-nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto['data-nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
}
proto.contentSize = result.contentSize;
// exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
@@ -1483,15 +1743,41 @@ export namespace DocUtils {
proto.lat = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
proto.lng = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
}
-
}
generatedDocuments.push(doc);
}
}
+ export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
+ const tbox = Docs.Create.TextDocument('', {
+ _xMargin: noMargins ? 0 : undefined,
+ _yMargin: noMargins ? 0 : undefined,
+ annotationOn,
+ docMaxAutoHeight: maxHeight,
+ backgroundColor: backgroundColor,
+ _width: width || 200,
+ _height: 35,
+ x: x,
+ y: y,
+ _fitWidth: true,
+ _autoHeight: true,
+ title,
+ });
+ const template = Doc.UserDoc().defaultTextLayout;
+ if (template instanceof Doc) {
+ tbox._width = NumCast(template._width);
+ tbox.layoutKey = 'layout_' + StrCast(template.title);
+ Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
+ }
+ return tbox;
+ }
+
export async function uploadYoutubeVideo(videoId: string, options: DocumentOptions) {
const generatedDocuments: Doc[] = [];
- for (const { source: { name, type }, result } of await Networking.UploadYoutubeToServer(videoId)) {
+ for (const {
+ source: { name, type },
+ result,
+ } of await Networking.UploadYoutubeToServer(videoId)) {
name && type && processFileupload(generatedDocuments, name, type, result, options);
}
return generatedDocuments;
@@ -1499,21 +1785,56 @@ export namespace DocUtils {
export async function uploadFilesToDocs(files: File[], options: DocumentOptions) {
const generatedDocuments: Doc[] = [];
const upfiles = await Networking.UploadFilesToServer(files);
- for (const { source: { name, type }, result } of upfiles) {
+ for (const {
+ source: { name, type },
+ result,
+ } of upfiles) {
name && type && processFileupload(generatedDocuments, name, type, result, options);
}
return generatedDocuments;
}
+
+ // copies the specified drag factory document
+ export function copyDragFactory(dragFactory: Doc) {
+ if (!dragFactory) return undefined;
+ const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
+ ndoc && Doc.AddDocToList(Doc.MyFileOrphans, 'data', Doc.GetProto(ndoc));
+ if (ndoc && dragFactory['dragFactory-count'] !== undefined) {
+ dragFactory['dragFactory-count'] = NumCast(dragFactory['dragFactory-count']) + 1;
+ Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory['dragFactory-count']).toString(), true);
+ }
+
+ if (ndoc && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, ndoc);
+
+ return ndoc;
+ }
+ export function delegateDragFactory(dragFactory: Doc) {
+ const ndoc = Doc.MakeDelegateWithProto(dragFactory);
+ if (ndoc && dragFactory['dragFactory-count'] !== undefined) {
+ dragFactory['dragFactory-count'] = NumCast(dragFactory['dragFactory-count']) + 1;
+ Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory['dragFactory-count']).toString();
+ }
+ return ndoc;
+ }
}
-ScriptingGlobals.add("Docs", Docs);
-ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; });
+ScriptingGlobals.add('Docs', Docs);
+ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) {
+ return DocUtils.copyDragFactory(dragFactory);
+});
+ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) {
+ return DocUtils.delegateDragFactory(dragFactory);
+});
+ScriptingGlobals.add(function makeDelegate(proto: any) {
+ const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
+ return d;
+});
ScriptingGlobals.add(function generateLinkTitle(self: Doc) {
- const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : "<?>";
- const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : "<?>";
- const relation = self.linkRelationship || "to";
+ const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : '<?>';
+ const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : '<?>';
+ const relation = self.linkRelationship || 'to';
return `${anchor1title} (${relation}) ${anchor2title}`;
});
ScriptingGlobals.add(function openTabAlias(tab: Doc) {
- CollectionDockingView.AddSplit(Doc.MakeAlias(tab), "right");
-}); \ No newline at end of file
+ CollectionDockingView.AddSplit(Doc.MakeAlias(tab), 'right');
+});
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 896237e1d..012ee163c 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -366,6 +366,7 @@
this._nOriginalY = 0;
this._bDragging = false;
+ this._bAborting = false;
this._fMove = lm.utils.fnBind(this.onMouseMove, this);
this._fUp = lm.utils.fnBind(this.onMouseUp, this);
@@ -387,8 +388,8 @@
},
onMouseDown: function (oEvent) {
- oEvent.preventDefault();
-
+ //oEvent.preventDefault(); // don't prevent deafult as this will stop text selection
+ if (oEvent.target && oEvent.target.className === "lm_title") return; // if the title is active and receiving events, then don't do tab dragging.
if (oEvent.button == 0 || oEvent.type === "touchstart") {
var coordinates = this._getCoordinates(oEvent);
@@ -427,6 +428,24 @@
}
},
+ AbortDrag: function () {
+ if (this._timeout != null) {
+ clearTimeout(this._timeout);
+ this._eBody.removeClass('lm_dragging');
+ this._eElement.removeClass('lm_dragging');
+ this._oDocument.find('iframe').css('pointer-events', '');
+ this._oDocument.unbind('mousemove touchmove', this._fMove);
+ this._oDocument.unbind('mouseup touchend', this._fUp);
+
+ if (this._bDragging === true) {
+ this._bDragging = false;
+ this._bAborting = true;
+ this.emit('dragStop', { pageX: 0, pageY: 0 }, this._nOriginalX + this._nX);
+ this._bAborting = false;
+ }
+ }
+ },
+
onMouseUp: function (oEvent) {
if (this._timeout != null) {
clearTimeout(this._timeout);
@@ -439,7 +458,14 @@
if (this._bDragging === true) {
this._bDragging = false;
this.emit('dragStop', oEvent, this._nOriginalX + this._nX);
+ } else { // make title receive pointer events to allow setting insertion position or selecting texst range
+ const classname = typeof oEvent.target?.className === "string" ? oEvent.target.className : "";
+ if (classname.includes("lm_title_wrap")) {
+ oEvent.target.children[0].style.pointerEvents = "all";
+ oEvent.target.children[0].focus();
+ }
}
+
}
},
@@ -1406,7 +1432,7 @@
this.root.callDownwards('_$init');
if (config.maximisedItemId === '__glMaximised') {
- this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise();
+ this.root.getItemsById(config.maximisedItemId)[0]?.toggleMaximise();
}
},
@@ -2178,18 +2204,18 @@
*/
_onDrop: function () {
this._layoutManager.dropTargetIndicator.hide();
-
+ let abortedDrop = this._dragListener._bAborting;
/*
* Valid drop area found
*/
- if (this._area !== null) {
+ if (!abortedDrop && this._area !== null) {
this._area.contentItem._$onDrop(this._contentItem, this._area);
/**
* No valid drop area available at present, but one has been found before.
* Use it
*/
- } else if (this._lastValidArea !== null) {
+ } else if (!abortedDrop && this._lastValidArea !== null) {
this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea);
/**
@@ -2197,7 +2223,7 @@
* content item to its original position if a original parent is provided.
* (Which is not the case if the drag had been initiated by createDragSource)
*/
- } else if (this._originalParent) {
+ } else if (!abortedDrop && this._originalParent) {
this._originalParent.addChild(this._contentItem);
/**
@@ -2212,7 +2238,7 @@
restoreScrollTops(this._contentItem.element)
this.element.remove();
- this._layoutManager.emit('itemDropped', this._contentItem);
+ !abortedDrop && this._layoutManager.emit('itemDropped', this._contentItem);
},
/**
@@ -2851,6 +2877,8 @@
this.closeElement = this.element.find('.lm_close_tab');
this.closeElement[contentItem.config.isClosable ? 'show' : 'hide']();
this.isActive = false;
+ this.titleElement[0].style.pointerEvents = "none"; // don't let title receive pointer events by default -- need to click on it to make it editable -- this allows the tab to be dragged
+ this.titleElement[0].onblur = () => this.titleElement[0].style.pointerEvents = "none";
this.setTitle(contentItem.config.title);
this.contentItem.on('titleChanged', this.setTitle, this);
@@ -2963,7 +2991,7 @@
if (this.contentItem.parent.isMaximised === true) {
this.contentItem.parent.toggleMaximise();
}
- new lm.controls.DragProxy(
+ let proxy = new lm.controls.DragProxy(
x,
y,
this._dragListener,
@@ -2971,6 +2999,7 @@
this.contentItem,
this.header.parent
);
+ this._layoutManager.emit('dragStart', proxy);
},
/**
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index c247afa26..0b5957fac 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,17 +1,17 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { convertToObject } from "typescript";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { BoolCast, StrCast, Cast } from "../../fields/Types";
-import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils";
-import { LightboxView } from "../views/LightboxView";
-import { MainViewModal } from "../views/MainViewModal";
-import "./CaptureManager.scss";
-import { SelectionManager } from "./SelectionManager";
-import { undoBatch } from "./UndoManager";
-const higflyout = require("@hig/flyout");
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { convertToObject } from 'typescript';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { BoolCast, StrCast, Cast } from '../../fields/Types';
+import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
+import { LightboxView } from '../views/LightboxView';
+import { MainViewModal } from '../views/MainViewModal';
+import './CaptureManager.scss';
+import { SelectionManager } from './SelectionManager';
+import { undoBatch } from './UndoManager';
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -22,32 +22,31 @@ export class CaptureManager extends React.Component<{}> {
@observable _document: any;
@observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
-
constructor(props: {}) {
super(props);
CaptureManager.Instance = this;
}
- public close = action(() => this.isOpen = false);
+ public close = action(() => (this.isOpen = false));
public open = action((doc: Doc) => {
this.isOpen = true;
this._document = doc;
});
-
@computed get visibilityContent() {
-
- return <div className="capture-block">
- <div className="capture-block-title">Visibility</div>
- <div className="capture-block-radio">
- <div className="radio-container">
- <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private
- </div>
- <div className="radio-container">
- <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public
+ return (
+ <div className="capture-block">
+ <div className="capture-block-title">Visibility</div>
+ <div className="capture-block-radio">
+ <div className="radio-container">
+ <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private
+ </div>
+ <div className="radio-container">
+ <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public
+ </div>
</div>
</div>
- </div>;
+ );
}
@computed get linksContent() {
@@ -66,75 +65,79 @@ export class CaptureManager extends React.Component<{}> {
order.push(
<div className="list-item">
<div className="number">{i}</div>
- {(l.anchor1 as Doc).title}
+ {StrCast((l.anchor1 as Doc).title)}
</div>
);
}
});
}
- return <div className="capture-block">
- <div className="capture-block-title">Links</div>
- <div className="capture-block-list">
- {order}
+ return (
+ <div className="capture-block">
+ <div className="capture-block-title">Links</div>
+ <div className="capture-block-list">{order}</div>
</div>
- </div>;
+ );
}
@computed get closeButtons() {
- return <div className="capture-block">
- <div className="buttons">
- <div className="save" onClick={() => {
- LightboxView.SetLightboxDoc(this._document);
- this.close();
- }}>
- Save
- </div>
- <div className="cancel" onClick={() => {
- const selected = SelectionManager.Views().slice();
- SelectionManager.DeselectAll();
- selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
- this.close();
- }}>
- Cancel
+ return (
+ <div className="capture-block">
+ <div className="buttons">
+ <div
+ className="save"
+ onClick={() => {
+ LightboxView.SetLightboxDoc(this._document);
+ this.close();
+ }}>
+ Save
+ </div>
+ <div
+ className="cancel"
+ onClick={() => {
+ const selected = SelectionManager.Views().slice();
+ SelectionManager.DeselectAll();
+ selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
+ this.close();
+ }}>
+ Cancel
+ </div>
</div>
</div>
- </div>;
+ );
}
-
-
private get captureInterface() {
- return <div className="capture-interface">
- <div className="capture-t1">
- <div className="recordButtonOutline" style={{}}>
- <div className="recordButtonInner" style={{}}>
+ return (
+ <div className="capture-interface">
+ <div className="capture-t1">
+ <div className="recordButtonOutline" style={{}}>
+ <div className="recordButtonInner" style={{}}></div>
</div>
+ Conversation Capture
</div>
- Conversation Capture
- </div>
- <div className="capture-t2">
-
- </div>
- {this.visibilityContent}
- {this.linksContent}
- <div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color="black" size={"lg"} />
+ <div className="capture-t2"></div>
+ {this.visibilityContent}
+ {this.linksContent}
+ <div className="close-button" onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ </div>
+ {this.closeButtons}
</div>
- {this.closeButtons}
- </div>;
-
+ );
}
render() {
- return <MainViewModal
- contents={this.captureInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: "500px", height: "350px", border: "none", background: "whitesmoke" }}
- overlayStyle={{ background: "black" }}
- overlayDisplayedOpacity={0.6}
- />;
+ return (
+ <MainViewModal
+ contents={this.captureInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ closeOnExternalClick={this.close}
+ dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }}
+ overlayStyle={{ background: 'black' }}
+ overlayDisplayedOpacity={0.6}
+ />
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 0db8bebd8..7856c913b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,627 +1,374 @@
-import { computed, observable, reaction } from "mobx";
+import { reaction } from "mobx";
import * as rp from 'request-promise';
-import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
-import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
-import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
+import { ScriptField } from "../../fields/ScriptField";
+import { Cast, DateCast, DocCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
-import { SharingPermissions } from "../../fields/util";
-import { Utils } from "../../Utils";
+import { SetCachedGroups, SharingPermissions } from "../../fields/util";
+import { OmitKeys, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
-import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
-import { DocumentType } from "../documents/DocumentTypes";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
-import { TreeView } from "../views/collections/TreeView";
+import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
+import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
+import { TreeViewType } from "../views/collections/CollectionTreeView";
+import { DashboardView } from "../views/DashboardView";
import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
-import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
-import { LabelBox } from "../views/nodes/LabelBox";
import { OverlayView } from "../views/OverlayView";
-import { DocumentManager } from "./DocumentManager";
import { DragManager } from "./DragManager";
-import { makeTemplate } from "./DropConverter";
-import { HistoryUtil } from "./History";
+import { MakeTemplate } from "./DropConverter";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
-import { SearchUtil } from "./SearchUtil";
-import { SelectionManager } from "./SelectionManager";
import { ColorScheme } from "./SettingsManager";
-import { SharingManager } from "./SharingManager";
-import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
interface Button {
+ // DocumentOptions fields a button can set
title?: string;
toolTip?: string;
icon?: string;
btnType?: ButtonType;
- click?: string;
numBtnType?: NumButtonType;
numBtnMin?: number;
numBtnMax?: number;
switchToggle?: boolean;
- script?: string;
width?: number;
- list?: string[];
+ btnList?: List<string>;
ignoreClick?: boolean;
buttonText?: string;
+
+ // fields that do not correspond to DocumentOption fields
+ scripts?: { script?: string; onClick?: string; }
+ funcs?: { [key:string]: string };
+ subMenu?: Button[];
}
export let resolvedPorts: { server: number, socket: number };
-const headerViewVersion = "0.1";
export class CurrentUserUtils {
- private static curr_id: string;
- //TODO tfs: these should be temporary...
- private static mainDocId: string | undefined;
-
- public static searchBtn: Doc;
- public static get id() { return this.curr_id; }
- public static get MainDocId() { return this.mainDocId; }
- public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
- @computed public static get UserDocument() { return Doc.UserDoc(); }
-
- @observable public static GuestTarget: Doc | undefined;
- @observable public static GuestDashboard: Doc | undefined;
- @observable public static GuestMobile: Doc | undefined;
- @observable public static propertiesWidth: number = 0;
- @observable public static searchPanelWidth: number = 0;
-
- // sets up the default User Templates - slideView, headerView
- static setupUserTemplateButtons(doc: Doc) {
- // 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.createToolButton({
- ignoreClick: true,
- icon: "mobile",
- btnType: ButtonType.ToolButton,
- backgroundColor: "transparent"
- }),
- this.mobileTextContainer({},
- [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]);
- doc["template-mobile-button"] = CurrentUserUtils.createToolButton({
- onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ToolButton,
- });
- }
- if (doc["template-button-slides"] === undefined) {
- const slideTemplate = Docs.Create.MultirowDocument(
- [
- Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }),
- Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) })
- ],
- { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }
- );
- slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
- doc["template-button-slides"] = CurrentUserUtils.createToolButton({
- onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card",
- btnType: ButtonType.ToolButton
- });
- }
-
- if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to.
- const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created
- linkTemplate.system = true;
- Doc.GetProto(linkTemplate).layout =
- "<div>" +
- " <FormattedTextBox {...props} dontSelectOnLoad={'true'} height='{this._headerHeight||75}px' ignoreAutoHeight={'true'} background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" +
- " <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
- "</div>";
- (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView");
-
- const rtf2 = {
- doc: {
- type: "doc", content: [
- {
- type: "paragraph",
- content: [{
- type: "dashField",
- attrs: {
- fieldKey: "src",
- hideKey: false
- }
- }]
- },
- { type: "paragraph" },
- {
- type: "paragraph",
- content: [{
- type: "dashField",
- attrs: {
- fieldKey: "dst",
- hideKey: false
- }
- }]
- }]
- },
- selection: { type: "text", anchor: 1, head: 1 },
- storedMarks: []
+ // initializes experimental advanced template views - slideView, headerView
+ static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) {
+ const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [
+ {
+ btnOpts: { title: "slide", icon: "address-card" },
+ templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true },
+ template: (opts:DocumentOptions) => Docs.Create.MultirowDocument(
+ [
+ Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }),
+ Docs.Create.TextDocument("", { title: "text", _fitWidth:true, _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) })
+ ], opts)
+ },
+ {
+ btnOpts: { title: "mobile", icon: "mobile" },
+ templateOpts: { title: "NEW MOBILE BUTTON", onClick: undefined, },
+ template: (opts:DocumentOptions) => this.mobileButton(opts,
+ [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }),
+ this.mobileTextContainer({},
+ [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])
+ ]
+ )
+ },
+ ];
+ const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => {
+ const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title);
+ const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' };
+ const assignBtnAndTempOpts = (templateBtn:Opt<Doc>, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => {
+ if (templateBtn) {
+ DocUtils.AssignOpts(templateBtn,btnOpts);
+ DocUtils.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions);
+ }
+ return templateBtn;
};
- linkTemplate.header = new RichTextField(JSON.stringify(rtf2), "");
+ return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts);
+ });
- doc["template-button-link"] = CurrentUserUtils.createToolButton({
- onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true,
- btnType: ButtonType.ToolButton
- });
- }
+ const reqdOpts:DocumentOptions = {
+ title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
+ _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true,
+ _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
+ };
+ const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
+ const reqdFuncs = { hidden: "IsNoviceMode()" };
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs);
+ }
+
+ /// Initializes templates for editing click funcs of a document
+ static setupChildClickEditors(doc: Doc, field = "clickFuncs-child") {
+ const tempClicks = DocCast(doc[field]);
+ const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true};
+ const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
+ { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"},
+ { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: "openOnRight(self.doubleClickView)"}];
+ const reqdClickList = reqdTempOpts.map(opts => {
+ const allOpts = {...reqdClickOpts, ...opts.opts};
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+ return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script,allOpts));
+ });
- // if (doc["template-button-switch"] === undefined) {
- // const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create;
-
- // const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true });
- // const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true });
- // const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true });
- // const labelTemplate = {
- // doc: {
- // type: "doc", content: [{
- // type: "paragraph",
- // content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }]
- // }]
- // },
- // selection: { type: "text", anchor: 1, head: 1 },
- // storedMarks: []
- // };
- // Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS");
- // Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'");
- // // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'");
- // // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true");
- // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]");
- // // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false");
- // const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true });
- // box.isTemplateDoc = makeTemplate(box, true, "switch");
-
- // doc["template-button-switch"] = CurrentUserUtils.createToolButton({
- // onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- // dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true,
- // btnType: ButtonType.ToolButton
- // });
- // }
-
- const requiredTypes = [
- doc["template-button-slides"] 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]
- ];
- if (doc["template-buttons"] === undefined) {
- doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
- title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
- hidden: ComputedField.MakeFunction("IsNoviceMode()") as any,
- _stayInCollection: true, _hideContextMenu: true, _forceActive: true,
- _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
- dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
- }));
- } else {
- const curButnTypes = Cast(doc["template-buttons"], Doc, null);
- DocListCastAsync(curButnTypes.data).then(async curBtns => {
- curBtns && await Promise.all(curBtns);
- requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
- });
- }
- return doc["template-buttons"] as Doc;
+ const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, system: true};
+ return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
}
- // setup the different note type skins
- static setupNoteTemplates(doc: Doc) {
- if (doc["template-note-Note"] === undefined) {
- const noteView = Docs.Create.TextDocument("", {
- title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true, icon: "sticky-note",
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- });
- noteView.isTemplateDoc = makeTemplate(noteView, true, "Note");
- doc["template-note-Note"] = new PrefetchProxy(noteView);
- }
- if (doc["template-note-Idea"] === undefined) {
- const noteView = Docs.Create.TextDocument("", {
- title: "text", backgroundColor: "pink", system: true, icon: "lightbulb",
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- });
- noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea");
- doc["template-note-Idea"] = new PrefetchProxy(noteView);
- }
- if (doc["template-note-Topic"] === undefined) {
- const noteView = Docs.Create.TextDocument("", {
- title: "text", backgroundColor: "lightblue", system: true, icon: "book-open",
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- });
- noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic");
- doc["template-note-Topic"] = new PrefetchProxy(noteView);
- }
- // if (doc["template-note-Todo"] === undefined) {
- // const noteView = Docs.Create.TextDocument("", {
- // title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption",
- // layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true,
- // _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- // });
- // noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo");
- // doc["template-note-Todo"] = new PrefetchProxy(noteView);
- // }
- // const taskStatusValues = [
- // { title: "todo", _backgroundColor: "blue", color: "white", system: true },
- // { title: "in progress", _backgroundColor: "yellow", color: "black", system: true },
- // { title: "completed", _backgroundColor: "green", color: "white", system: true }
- // ];
- // if (doc.fieldTypes === undefined) {
- // doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations", system: true });
- // DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues);
- // }
-
- if (doc["template-notes"] === undefined) {
- doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc], // doc["template-note-Todo"] as any as Doc],
- { title: "Note Layouts", _height: 75, system: true }));
- } else {
- const curNoteTypes = Cast(doc["template-notes"], Doc, null);
- const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc];
- DocListCastAsync(curNoteTypes.data).then(async curNotes => {
- curNotes && await Promise.all(curNotes);
- requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype));
- });
- }
+ /// Initializes templates for editing click funcs of a document
+ static setupClickEditorTemplates(doc: Doc, field = "template-clickFuncs") {
+ const tempClicks = DocCast(doc[field]);
+ const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, system: true};
+ const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
+ { opts: { title: "onClick"}, script: "console.log( 'click')"},
+ { opts: { title: "onDoubleClick"}, script: "console.log( 'double click')"},
+ { opts: { title: "onChildClick"}, script: "console.log( 'child click')"},
+ { opts: { title: "onChildDoubleClick"}, script: "console.log( 'child double click')"},
+ { opts: { title: "onCheckedClick"}, script: "console.log( heading, checked, containingTreeView)"},
+ ];
+ const reqdClickList = reqdTempOpts.map(opts => {
+ const allOpts = {...reqdClickOpts, ...opts.opts};
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+ return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}), allOpts), true, opts.opts.title);
+ });
- return doc["template-notes"] as Doc;
- }
+ const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, system: true};
+ return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
+ }
+
+ /// Initializes templates that can be applied to notes
+ static setupNoteTemplates(doc: Doc, field="template-notes") {
+ const tempNotes = DocCast(doc[field]);
+ const reqdTempOpts:DocumentOptions[] = [
+ { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"},
+ { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
+ { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
+ const reqdNoteList = reqdTempOpts.map(opts => {
+ const reqdOpts = {...opts, title: "text", width:200, system: true};
+ const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined;
+ return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note");
+ });
- // creates Note templates, and initial "user" templates
- static setupDocTemplates(doc: Doc) {
- const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc);
- const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc);
- const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc);
- if (doc.templateDocs === undefined) {
- doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], {
- title: "template layouts", _xMargin: 0, system: true,
- dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
- }));
- }
+ const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, system: true };
+ return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
}
- // setup templates for different document types when they are iconified from Document Decorations
- static setupDefaultIconTemplates(doc: Doc) {
- if (doc["template-icon-view"] === undefined) {
- const iconView = Docs.Create.LabelDocument({
- title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray",
- _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
- });
- // Docs.Create.TextDocument("", {
- // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
- // });
- // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
- iconView.isTemplateDoc = makeTemplate(iconView);
- doc["template-icon-view"] = new PrefetchProxy(iconView);
- }
- if (doc["template-icon-view-rtf"] === undefined) {
- const iconRtfView = Docs.Create.LabelDocument({
- title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"),
- _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
- });
- iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
- doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
- }
- if (doc["template-icon-view-button"] === undefined) {
- const iconBtnView = Docs.Create.FontIconDocument({
- title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30,
- _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
- });
- iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON);
- doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView);
- }
- if (doc["template-icon-view-img"] === undefined) {
- const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
- title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
- });
- iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG);
- doc["template-icon-view-img"] = new PrefetchProxy(iconImageView);
- }
- if (doc["template-icon-view-col"] === undefined) {
- const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true });
- iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL);
- doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
- }
- if (doc["template-icons"] === undefined) {
- doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true }));
- } 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-button"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
- DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
- curIcons && await Promise.all(curIcons);
- requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
- });
- }
- return doc["template-icons"] as Doc;
+ /// Initializes collection of templates for notes and click functions
+ static setupDocTemplates(doc: Doc, field="myTemplates") {
+ DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"});
+ const templates = [
+ CurrentUserUtils.setupNoteTemplates(doc),
+ CurrentUserUtils.setupClickEditorTemplates(doc)
+ ];
+ CurrentUserUtils.setupChildClickEditors(doc)
+ const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, };
+ const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
+ return DocUtils.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts);
}
+ // setup templates for different document types when they are iconified from Document Decorations
+ static setupDefaultIconTemplates(doc: Doc, field="template-icons") {
+ const reqdOpts = { title: "icon templates", _height: 75, system: true };
+ const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
+
+ const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
+ const iconFieldName = "icon" + (type ? "_" + type : "");
+ const curIcon = DocCast(templateIconsDoc[iconFieldName]);
+ let creator = labelBox;
+ switch (opts.iconTemplate) {
+ case DocumentType.IMG : creator = imageBox; break;
+ case DocumentType.FONTICON: creator = fontBox; break;
+ }
+ const allopts = {system: true, ...opts};
+ return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ?
+ DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
+ {onClick:"deiconifyView(documentView)"});
+ };
+ const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
+ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+ });
+ const imageBox = (opts: DocumentOptions, url?:string) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", ...opts });
+ const fontBox = (opts:DocumentOptions, data?:string) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
+ const iconTemplates = [
+ makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}),
+ makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}),
+ makeIconTemplate(DocumentType.PDF, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "pink"}),
+ makeIconTemplate(DocumentType.WEB, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "brown"}),
+ makeIconTemplate(DocumentType.RTF, "text", { iconTemplate:DocumentType.LABEL, _showTitle: "creationDate"}),
+ makeIconTemplate(DocumentType.IMG, "data", { iconTemplate:DocumentType.IMG, _height: undefined}),
+ makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}),
+ makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}),
+ makeIconTemplate(DocumentType.BUTTON,"data", { iconTemplate:DocumentType.FONTICON}),
+ //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
+ makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }),
+ //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
+ ].filter(d => d).map(d => d!);
+ DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates);
+ }
+
+ /// initalizes the set of "empty<DocType>" versions of each document type with default fields. e.g.,. emptyNote, emptyPresentation
static creatorBtnDescriptors(doc: Doc): {
- title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean,
- click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc
+ title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc,
+ backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string},
}[] {
- //TODO: we may need to add in note-teking view here
- if (doc.emptyPresentation === undefined) {
- doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyCollection === undefined) {
- doc.emptyCollection = Docs.Create.FreeformDocument([],
- { _nativeWidth: undefined, _nativeHeight: undefined, _fitWidth: true, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyPane === undefined) {
- doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _backgroundGridShow: true, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptySlide === undefined) {
- const textDoc = Docs.Create.TreeDocument([], {
- title: "Slide", _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true,
- allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true,
- backgroundColor: "transparent", system: true, cloneFieldFilter: new List<string>(["system"])
- });
- Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text');
- FormattedTextBox.SelectOnLoad = textDoc[Id];
- doc.emptySlide = textDoc;
- }
- if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) {
- const json = {
- doc: {
- type: "doc",
- content: [
- {
- type: "paragraph", attrs: {}, content: [{
- type: "dashField",
- attrs: { fieldKey: "author", docid: "", hideKey: false },
- marks: [{ type: "strong" }]
- }, {
- type: "dashField",
- attrs: { fieldKey: "creationDate", docid: "", hideKey: false },
- marks: [{ type: "strong" }]
- }]
+ const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List<string>(["system"]) });
+ const json = {
+ doc: {
+ type: "doc",
+ content: [
+ {
+ type: "paragraph", attrs: {}, content: [{
+ type: "dashField",
+ attrs: { fieldKey: "author", docid: "", hideKey: false },
+ marks: [{ type: "strong" }]
+ }, {
+ type: "dashField",
+ attrs: { fieldKey: "creationDate", docid: "", hideKey: false },
+ marks: [{ type: "strong" }]
}]
- },
- selection: { type: "text", anchor: 1, head: 1 },
- storedMarks: []
- };
- const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), {
- title: "text", version: headerViewVersion, _height: 70, _headerPointerEvents: "all",
- _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true,
- cloneFieldFilter: new List<string>(["system"])
- }, "header");
- const headerBtnHgt = 10;
- headerTemplate[DataSym].layout =
+ }]
+ },
+ selection: { type: "text", anchor: 1, head: 1 },
+ storedMarks: []
+ };
+ const headerBtnHgt = 10;
+ const headerTemplate = (opts:DocumentOptions) => {
+ const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text",
+ layout:
"<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>" +
` <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
" <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' background='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
` <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' background='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’} >Metadata</HTMLdiv>` +
- "</HTMLdiv>";
+ "</HTMLdiv>"
+ }, "header");
// "<div style={'height:100%'}>" +
// " <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.mySharedDocs.userColor}' />" +
// " <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
// "</div>";
- (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
- doc.emptyHeader = headerTemplate;
- ((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyComparison === undefined) {
- doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "comparison box", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) });
- }
- if (doc.emptyScript === undefined) {
- doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyScreenshot === undefined) {
- doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, title: "empty screenshot", _width: 400, _height: 200, system: true, cloneFieldFilter: new List<string>(["system"]) });
- }
- if (doc.emptyWall === undefined) {
- doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List<string>(["system"]) });
- (doc.emptyWall as Doc).videoWall = true;
- }
- if (doc.emptyAudio === undefined) {
- doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyNote === undefined) {
- doc.emptyNote = Docs.Create.TextDocument("", {
- _width: 200, title: "text note", _autoHeight: true, system: true,
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- cloneFieldFilter: new List<string>(["system"])
- });
- ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyImage === undefined) {
- doc.emptyImage = 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", system: true });
- }
- if (doc.emptyButton === undefined) {
- doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyWebpage === undefined) {
- doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List<string>(["system"]) });
- }
- if (doc.emptyMap === undefined) {
- doc.emptyMap = Docs.Create.MapDocument([], { title: "map", _showSidebar: true, _width: 800, _height: 600, system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyMap as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.activeMobileMenu === undefined) {
- this.setupActiveMobileMenu(doc);
- }
- return [
- { toolTip: "Tap to create a note in a new pane, drag for a note", title: "Note", icon: "sticky-note", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyNote as Doc, noviceMode: true, clickFactory: doc.emptyNote as Doc, },
- { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, },
- { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true },
- { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc },
- { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc },
- { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true },
- { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc },
- { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc },
- { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
- { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc },
- // { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
- { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc },
- { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
- { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc },
- { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
- { toolTip: "Tap to create a map in the new pane, drag for a map", title: "Map", icon: "map-marker-alt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyMap as Doc, noviceMode: true }
- ];
+ MakeTemplate(Doc.GetProto(header), true, "Untitled Header");
+ return header;
+ }
+ const emptyThings:{key:string, // the field name where the empty thing will be stored
+ opts:DocumentOptions, // the document options that are required for the empty thing
+ funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing
+ creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist
+ }[] = [
+ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }},
+ {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200 }},
+ {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }},
+ {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _fitWidth:false, _backgroundGridShow: true, }},
+ {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }},
+ {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }},
+ {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }},
+ {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }},
+ {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }},
+ {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List<string>(["system"]) }},
+ {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }},
+ {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
+ // {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }},
+ {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}},
+ {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }},
+ {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }},
+ {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree,
+ treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true,
+ allowOverlayDrop: true, treeViewType: TreeViewType.outline,
+ backgroundColor: "white", _xMargin: 0, _yMargin: 0, _singleLine: true
+ }, funcs: {title: 'self.text?.Text'}},
+ ];
- }
+ emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs));
+
+ return [
+ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, },
+ { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, },
+ { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab), scripts: { onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, },
+ { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, },
+ { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, },
+ { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, },
+ { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, },
+ { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, },
+ { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'} },
+ { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'}},
+ { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} },
+ { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}},
+ { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, },
+ { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, },
+ { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } },
+ ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, }))
+ }
+
+ /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
+ static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
+ const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
+ const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
+ const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
+ _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias",
+ btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true,
+ _removeDropProperties: new List<string>(["_stayInCollection"]),
+ };
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs);
+ });
- // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
- static async setupCreatorButtons(doc: Doc) {
- let alreadyCreatedButtons: string[] = [];
- const dragCreatorSet = Cast(doc.myItemCreators, Doc, null);
- if (dragCreatorSet) {
- const dragCreators = Cast(dragCreatorSet.data, listSpec(Doc));
- if (dragCreators) {
- const dragDocs = await Promise.all(Array.from(dragCreators));
- alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title));
- }
- }
- const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
- const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({
- _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35,
- icon,
- title,
- toolTip,
- btnType: ButtonType.ToolButton,
- ignoreClick,
- _dropAction: "alias",
- onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined,
- onClick: click ? ScriptField.MakeScript(click) : undefined,
- backgroundColor: backgroundColor ? backgroundColor : Colors.DARK_GRAY,
- color: Colors.WHITE,
- _hideContextMenu: true,
- _removeDropProperties: new List<string>(["_stayInCollection"]),
- _stayInCollection: true,
- dragFactory,
- clickFactory,
- hidden: !noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined,
- system: true,
- }));
-
- if (dragCreatorSet === undefined) {
- doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
- title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
- _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
- dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
- }));
- } else {
- creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb));
- }
- return doc.myItemCreators as Doc;
+ const reqdOpts:DocumentOptions = {
+ title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true,
+ _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
+ childDocumentsActive: true
+ };
+ const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
}
- static async menuBtnDescriptions(doc: Doc) {
+ /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents
+ static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}}[] {
+ const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())";
return [
- { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' },
- { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' },
- { title: "Files", target: Cast(doc.myFilesystem, Doc, null), icon: "folder-open", click: 'selectMainMenu(self)' },
- { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
- { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
- { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' },
- { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
- { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
- { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
- ];
- }
-
- static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
- if (doc.menuStack === undefined) {
- await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing
- const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments, hidden }) =>
- Docs.Create.FontIconDocument({
- icon,
- btnType: ButtonType.MenuButton,
- _stayInCollection: true,
- _hideContextMenu: true,
- _chromeHidden: true,
- system: true,
- dontUndo: true,
- title,
- target,
- hidden: hidden ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined,
- _dropAction: "alias",
- _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
- _width: 60,
- _height: 60,
- watchedDocuments,
- onClick: ScriptField.MakeScript(click, { scriptContext: "any" })
- })
- );
-
- menuBtns.forEach(menuBtn => {
- if (menuBtn.title === "Search") {
- this.searchBtn = menuBtn;
- }
- });
-
- menuBtns.forEach(menuBtn => {
- if (menuBtn.title === "Search") {
- doc.searchBtn = menuBtn;
- }
- });
-
- doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, {
- title: "menuItemPanel",
- childDropAction: "alias",
- _chromeHidden: true,
- backgroundColor: Colors.DARK_GRAY,
- boxShadow: "rgba(0,0,0,0)",
- dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
- ignoreClick: true,
- _gridGap: 0,
- _yMargin: 0,
- _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
- }));
- }
- // this resets all sidebar buttons to being deactivated
- PromiseValue(Cast(doc.menuStack, Doc)).then(stack => {
- stack && PromiseValue(stack.data).then(btns => {
- DocListCastAsync(btns).then(bts => bts?.forEach(btn => {
- btn.dontUndo = true;
- btn.system = true;
- if (btn.title === "Catalog" || btn.title === "My Files") { // migration from Catalog to My Files
- btn.target = Doc.UserDoc().myFilesystem;
- btn.title = "My Files";
- }
- }));
- });
+ { title: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", },
+ { title: "Search", target: this.setupSearcher(doc, "mySearcher"), icon: "search", },
+ { title: "Files", target: this.setupFilesystem(doc, "myFilesystem"), icon: "folder-open", },
+ { title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", },
+ { title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", },
+ { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs: {badgeValue:badgeValue}},
+ { title: "Trails", target: this.setupTrails(doc, "myTrails"), icon: "pres-trail", },
+ { title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
+ ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}}));
+ }
+
+ /// the empty panel that is filled with whichever left menu button's panel has been selected
+ static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") {
+ DocUtils.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.system = true; return doc;})(new Doc()), {system:true});
+ }
+
+ /// Initializes the left sidebar menu buttons and the panels they open up
+ static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") {
+ this.setupLeftSidebarPanel(doc);
+ const myLeftSidebarMenu = DocCast(doc[field]);
+ const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => {
+ const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
+ const reqdBtnOpts:DocumentOptions = {
+ title, icon, target, btnType: ButtonType.MenuButton, system: true, dontUndo: true, dontRegisterView: true,
+ _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias",
+ _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
+ };
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
});
- return doc.menuStack as Doc;
- }
-
- // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
- static setupActiveMobileMenu(doc: Doc) {
- if (doc.activeMobileMenu === undefined) {
- doc.activeMobileMenu = this.setupMobileMenu();
- }
- return doc.activeMobileMenu as Doc;
+ const reqdStackOpts:DocumentOptions ={
+ title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
+ _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
+ };
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
- // Sets up mobileMenu stacking document
- static setupMobileMenu() {
- const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), {
- _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,
- }));
- return menu;
+ // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
+ static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") {
+ const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,};
+ DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts);
}
- // SEts up mobile buttons for inside mobile menu
+ // Sets up mobile buttons for inside mobile menu
static setupMobileButtons(doc?: Doc, buttons?: string[]) {
+ return [];
const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
{ title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards 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." },
@@ -669,42 +416,6 @@ export class CurrentUserUtils {
}) as any as Doc
- static setupThumbButtons(doc: Doc) {
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
- { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" },
- { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" },
- { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange" },
- { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" },
- { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" },
- ];
- return docProtoData.map(data => Docs.Create.FontIconDocument({
- _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon,
- _dropAction: data.pointerDown ? "copy" : undefined,
- ignoreClick: data.ignoreClick,
- onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
- clipboard: data.clipboard,
- onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined,
- onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
- backgroundColor: data.backgroundColor,
- _removeDropProperties: new List<string>(["dropAction"]),
- dragFactory: data.dragFactory,
- system: true
- }));
- }
-
- static setupThumbDoc(userDoc: Doc) {
- if (!userDoc.thumbDoc) {
- const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
- _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons",
- _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true
- });
- thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], {
- _width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true
- });
- userDoc.thumbDoc = thumbDoc;
- }
- return Cast(userDoc.thumbDoc, Doc);
- }
static setupMobileInkingDoc(userDoc: Doc) {
return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", system: true });
@@ -723,608 +434,433 @@ export class CurrentUserUtils {
});
}
- static setupLibrary(userDoc: Doc) {
- return CurrentUserUtils.setupDashboards(userDoc);
+ /// Search option on the left side button panel
+ static setupSearcher(doc: Doc, field:string) {
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
+ dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias",
+ _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, });
}
- // 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) {
- // setup a masonry view of all he creators
- const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc);
- const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc);
-
- doc["tabs-button-tools"] = undefined;
-
- if (doc.myCreators === undefined) {
- doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], {
- title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true,
- _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true,
- }));
- }
- // setup a color picker
- if (doc.myColorPicker === undefined) {
- const color = Docs.Create.ColorDocument({
- title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List<string>(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true
- });
- doc.myColorPicker = new PrefetchProxy(color);
- }
-
- if (doc.myTools === undefined) {
- const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], {
- title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true,
- system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0",
- })) as any as Doc;
-
- doc.myTools = toolsStack;
- }
- }
-
- static async setupDashboards(doc: Doc) {
- // setup dashboards library item
- await doc.myDashboards;
- if (doc.myDashboards === undefined) {
- const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`);
- const newDashboardButton: Doc = Docs.Create.FontIconDocument({ onClick: newDashboard, _forceActive: true, toolTip: "Create new dashboard", _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
- doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, freezeChildren: "remove|add",
- treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true,
- explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
- }));
- const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`);
- const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
- const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
- const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`);
- const removeDashboard = ScriptField.MakeScript('removeDashboard(self)');
- const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()');
- // (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, shareDashboard!, removeDashboard!]);
- // (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]);
- // (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "user-friends", "times"]);
- (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
- (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
- (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "chalkboard", "tv", "camera", "users", "times"]);
- (doc.myDashboards as any as Doc).childContextMenuFilters = new List<ScriptField>([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]);
- }
- return doc.myDashboards as any as Doc;
- }
-
- static async setupPresentations(doc: Doc) {
- await doc.myTrails;
- if (doc.myTrails === undefined) {
- const newTrail = ScriptField.MakeScript(`createNewPresentation()`);
- const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "Create new trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
- doc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "My Trails", _showTitle: "title", _height: 100,
- treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true,
- explainer: "All of the trails that you have created will appear here."
- }));
- (doc.myTrails as any as Doc).contextMenuScripts = new List<ScriptField>([newTrail!]);
- (doc.myTrails as any as Doc).contextMenuLabels = new List<string>(["Create New Trail"]);
- (doc.myTrails as any as Doc).childContextMenuIcons = new List<string>(["plus"]);
- }
- return doc.myTrails as any as Doc;
- }
-
- static async setupFilesystem(doc: Doc) {
- await doc.myFilesystem;
- if (doc.myFilesystem === undefined) {
- doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
- // doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true });
- const newFolder = ScriptField.MakeFunction(`makeTopLevelFolder()`, { scriptContext: "any" })!;
- const newFolderButton: Doc = Docs.Create.FontIconDocument({
- onClick: newFolder, _forceActive: true, toolTip: "Create new folder",
- _stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30,
- buttonText: "New folder", icon: "folder-plus", system: true
- });
- doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileOrphans as Doc], {
- title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
- treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, ignoreClick: true,
- isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
- explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
- }));
- (doc.myFilesystem as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder]);
- (doc.myFilesystem as any as Doc).contextMenuLabels = new List<string>(["Create new folder"]);
- (doc.myFilesystem as any as Doc).childContextMenuIcons = new List<string>(["plus"]);
- }
- return doc.myFilesystem as any as Doc;
- }
-
- static setupRecentlyClosedDocs(doc: Doc) {
- if (doc.myRecentlyClosedDocs === undefined) {
- const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
- const clearDocsButton: Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true });
- doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true,
- treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, ignoreClick: true,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true,
- explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
-
- }));
- (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
- (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List<string>(["Empty recently closed"]);
- (doc.myRecentlyClosedDocs as any as Doc).contextMenuIcons = new List<string>(["trash"]);
-
- }
- }
+ /// Initializes the panel of draggable tools that is opened from the left sidebar.
+ static setupToolsBtnPanel(doc: Doc, field:string) {
+ const myTools = DocCast(doc[field]);
+ const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined);
+ const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
+ const reqdToolOps:DocumentOptions = {
+ title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0",
+ _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
+ };
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
+ }
+
+ /// initializes the left sidebar dashboard pane
+ static setupDashboards(doc: Doc, field:string) {
+ var myDashboards = DocCast(doc[field]);
+
+ const newDashboard = `createNewDashboard()`;
+ const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true,
+ title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true };
+ const reqdBtnScript = {onClick: newDashboard,}
+ const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
+
+ const reqdOpts:DocumentOptions = {
+ title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true,
+ targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true,
+ buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias",
+ _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
+ contextMenuLabels: new List<string>(["Create New Dashboard"]),
+ contextMenuIcons: new List<string>(["plus"]),
+ childContextMenuLabels: new List<string>(["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]),// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
+ childContextMenuIcons: new List<string>(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
+ explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
+ };
+ myDashboards = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
+ const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
+ const contextMenuScripts = [newDashboard];
+ const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
+ const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
+ if (Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) {
+ myDashboards.contextMenuScripts = new List<ScriptField>(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
+ }
+ if (Cast(myDashboards.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) {
+ myDashboards.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
+ }
+ if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) {
+ myDashboards.childContextMenuFilters = new List<ScriptField>(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!));
+ }
+ return myDashboards;
+ }
+
+ /// initializes the left sidebar Trails pane
+ static setupTrails(doc: Doc, field:string) {
+ var myTrails = DocCast(doc[field]);
+ const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true,
+ title: "New trail", toolTip: "Create new trail", btnType: ButtonType.ClickButton, buttonText: "New trail", icon: "plus", system: true };
+ const reqdBtnScript = {onClick: `createNewPresentation()`};
+ const newTrailButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
+
+ const reqdOpts:DocumentOptions = {
+ title: "My Trails", _showTitle: "title", _height: 100,
+ treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
+ treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton,
+ contextMenuIcons: new List<string>(["plus"]),
+ contextMenuLabels: new List<string>(["Create New Trail"]),
+ _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true,
+ explainer: "All of the trails that you have created will appear here."
+ };
+ myTrails = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
+ const contextMenuScripts = [reqdBtnScript.onClick];
+ if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) {
+ myTrails.contextMenuScripts = new List<ScriptField>(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
+ }
+ return myTrails;
+ }
+
+ /// initializes the left sidebar File system pane
+ static setupFilesystem(doc: Doc, field:string) {
+ var myFilesystem = DocCast(doc[field]);
+ const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
+
+ const newFolder = `TreeView_addNewFolder()`;
+ const newFolderOpts: DocumentOptions = {
+ _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _width: 30, _height: 30,
+ title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", system: true
+ };
+ const newFolderScript = { onClick: newFolder};
+ const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
+
+ const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true,
+ title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true,
+ isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true,
+ treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias",
+ childContextMenuLabels: new List<string>(["Create new folder"]),
+ childContextMenuIcons: new List<string>(["plus"]),
+ explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
+ };
+ myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]);
+ const childContextMenuScripts = [newFolder];
+ if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) {
+ myFilesystem.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
+ }
+ return myFilesystem;
+ }
+
+ /// initializes the panel displaying docs that have been recently closed
+ static setupRecentlyClosed(doc: Doc, field:string) {
+ const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true,
+ title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true,
+ treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
+ contextMenuLabels: new List<string>(["Empty recently closed"]),
+ contextMenuIcons:new List<string>(["trash"]),
+ explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
+ };
+ const recentlyClosed = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
- static setupFilterDocs(doc: Doc) {
- // setup Filter item
- if (doc.currentFilter === undefined) {
- doc.currentFilter = Docs.Create.FilterDocument({
- title: "Unnamed Filter", _height: 150,
- treeViewHideTitle: true, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, childDropAction: "none",
- treeViewTruncateTitleWidth: 150, ignoreClick: true,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, _autoHeight: true, _fitWidth: true
- });
- const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
- (doc.currentFilter as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
- (doc.currentFilter as Doc).contextMenuLabels = new List<string>(["Clear All"]);
- (doc.currentFilter as Doc).filterBoolean = "AND";
- }
- }
+ const clearAll = (target:string) => `getProto(${target}).data = new List([])`;
+ const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true,
+ title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", system: true,
+ toolTip: "Empty recently closed",};
+ const clearDocsButton = DocUtils.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
- static setupUserDoc(doc: Doc) {
- if (doc.myUserDoc === undefined) {
- doc.treeViewOpen = true;
- doc.treeViewExpandedView = "fields";
- doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], {
- treeViewHideTitle: true, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title",
- treeViewTruncateTitleWidth: 150, ignoreClick: true,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
- })) as any as Doc;
+ if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton;
+
+ if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) {
+ recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
}
+ return recentlyClosed;
}
- static setupSidebarContainer(doc: Doc) {
- if (doc.sidebar === undefined) {
- const sidebarContainer = new Doc();
- sidebarContainer.system = true;
- doc.sidebar = new PrefetchProxy(sidebarContainer);
- }
- return doc.sidebar as Doc;
- }
-
- // setup the list of sidebar mode buttons which determine what is displayed in the sidebar
- static async setupSidebarButtons(doc: Doc) {
- CurrentUserUtils.setupSidebarContainer(doc);
- await CurrentUserUtils.setupToolsBtnPanel(doc);
- CurrentUserUtils.setupImportSidebar(doc);
- CurrentUserUtils.setupDashboards(doc);
- CurrentUserUtils.setupPresentations(doc);
- CurrentUserUtils.setupFilesystem(doc);
- CurrentUserUtils.setupRecentlyClosedDocs(doc);
- CurrentUserUtils.setupUserDoc(doc);
+ /// initializes the left sidebar panel view of the UserDoc
+ static setupUserDocView(doc: Doc, field:string) {
+ const reqdOpts:DocumentOptions = {
+ _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
+ boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true,
+ treeViewHideTitle: true, treeViewTruncateTitleWidth: 150
+ };
+ if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]);
}
- static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, {
+ static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, {
...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
_lockedPosition: true, system: true, flexDirection: "row"
- })) as any as Doc
-
- static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
- ...opts, btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _removeDropProperties: new List<string>(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true
- })) as any as Doc
-
- /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
- static setupDockedButtons(doc: Doc) {
- if (doc["dockedBtn-undo"] === undefined) {
- doc["dockedBtn-undo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("undo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "undo", icon: "undo-alt", system: true });
- }
- if (doc["dockedBtn-redo"] === undefined) {
- doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("redo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "redo", icon: "redo-alt", system: true });
- }
- if (doc.dockedBtns === undefined) {
- doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
- }
- (doc["dockedBtn-undo"] as Doc).dontUndo = true;
- (doc["dockedBtn-redo"] as Doc).dontUndo = true;
- }
-
- static textTools(doc: Doc) {
- const tools: Button[] =
- [
- {
- title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true,
- list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia",
- "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"],
- script: 'setFont(value, _readOnly_)'
- },
- { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' },
- { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor(value, _readOnly_)' },
- { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold(_readOnly_)' },
- { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic(_readOnly_)' },
- { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' },
- { title: "Bullet List", toolTip: "Bullet", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' },
- { title: "Number List", toolTip: "Number", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal", _readOnly_)' },
-
- // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'},
- // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'},
- // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'},
- { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' },
- { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' },
- { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' },
- ];
- return tools;
+ })
+
+ static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
+ btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true,
+ _removeDropProperties: new List<string>(["_dropAction", "_hideContextMenu", "stayInCollection"]),
+ _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts,
+ })
+
+ /// initializes the required buttons in the expanding button menu at the bottom of the Dash window
+ static setupDockedButtons(doc: Doc, field="myDockedBtns") {
+ const dockedBtns = DocCast(doc[field]);
+ const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}) =>
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ CurrentUserUtils.createToolButton(opts), scripts);
+
+ const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
+ { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }},
+ { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }}
+ ];
+ const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
+ const dockBtnsReqdOpts = {
+ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true,
+ childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true
+ };
+ reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
+ reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+ return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
}
- static inkTools(doc: Doc) {
- const tools: Button[] = [
- { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen", _readOnly_)' },
- { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser", _readOnly_)' },
- // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' },
- { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle", _readOnly_)' },
- // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' },
- { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line", _readOnly_)' },
- { title: "Fill color", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" },
- { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' },
- { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' },
+ static textTools():Button[] {
+ return [
+ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'},
+ btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
+ { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
+ { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_); }'}},
+ { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} },
+ { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} },
+ { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} },
+ { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} },
+ { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} },
+
+ // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
+ // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
+ // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}},
+ { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }},
+ { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} },
+ { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} },
+ { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}},
];
- return tools;
}
- static schemaTools(doc: Doc) {
- const tools: Button[] =
- [
- {
- title: "Show preview",
- toolTip: "Show preview of selected document",
- btnType: ButtonType.ToggleButton,
- buttonText: "Show Preview",
- icon: "eye",
- click: 'toggleSchemaPreview(_readOnly_)',
- },
- ];
- return tools;
+ static inkTools():Button[] {
+ return [
+ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", _readOnly_);}' }},
+ { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", _readOnly_);}'} },
+ { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", _readOnly_);}' }},
+ // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
+ { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} },
+ // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' },
+ { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick: '{ return setActiveTool("line", _readOnly_);}' }},
+ { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: "{ return setFillColor(value, _readOnly_);}"} },
+ { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
+ { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} },
+ ];
}
- static webTools(doc: Doc) {
- const tools: Button[] =
- [
- { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack(_readOnly_)' },
- { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward(_readOnly_)' },
- //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' },
- { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL(value, _readOnly_)' },
- ];
-
- return tools;
+ static schemaTools():Button[] {
+ return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{return toggleSchemaPreview(_readOnly_);}'}, }];
}
- static async contextMenuTools(doc: Doc) {
+ static webTools() {
return [
- {
- title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true,
- list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
- CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn,
- CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
- CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
- CollectionViewType.Grid, CollectionViewType.NoteTaking],
- script: 'setView(value, _readOnly_)',
- }, // Always show
- {
- title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip",
- script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()'
- }, // Only when a document is selected
- {
- title: "Header Color", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading",
- script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()',
- }, // Only when a document is selected
- { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay(_readOnly_)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform
- // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected
- { title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available
- { title: "Ink", type: "inkTools", subMenu: true, expanded: 'selectedDocumentType("ink")' }, // Always available
- { title: "Web", type: "webTools", subMenu: true, hidden: 'selectedDocumentType("web")' }, // Only when Web is selected
- { title: "Schema", type: "schemaTools", subMenu: true, hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected
+ { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(_readOnly_); }' }},
+ { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(_readOnly_); }'}},
+ //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' },
+ { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
];
}
- // Sets up the default context menu buttons
- static async setupContextMenuButtons(doc: Doc) {
- if (doc.contextMenuBtns === undefined) {
- const docList: Doc[] = [];
-
- (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => {
- const menuDocList: Doc[] = [];
- if (subMenu) {
- // default is textTools
- let tools: Button[];
- switch (type) {
- case "inkTools":
- tools = CurrentUserUtils.inkTools(doc);
- break;
- case "schemaTools":
- tools = CurrentUserUtils.schemaTools(doc);
- break;
- case "webTools":
- tools = CurrentUserUtils.webTools(doc);
- break;
- case "textTools":
- tools = CurrentUserUtils.textTools(doc);
- break;
- default:
- tools = CurrentUserUtils.textTools(doc);
- break;
- }
- tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle }) => {
- const computed = click ? ComputedField.MakeFunction(click) as any : "transparent";
- menuDocList.push(Docs.Create.FontIconDocument({
- _nativeWidth: width ? width : 25,
- _nativeHeight: 25,
- _width: width ? width : 25,
- _height: 25,
- icon,
- toolTip,
- numBtnType,
- numBtnMin,
- numBtnMax,
- script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined,
- btnType: btnType,
- btnList: new List<string>(list),
- ignoreClick: ignoreClick,
- _stayInCollection: true,
- _hideContextMenu: true,
- _lockedPosition: true,
- system: true,
- dontUndo: true,
- title,
- switchToggle,
- color: Colors.WHITE,
- backgroundColor: computed,
- _dropAction: "alias",
- _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
- onClick: click ? ScriptField.MakeScript(click) : undefined
- }));
- });
- docList.push(CurrentUserUtils.linearButtonList({
- linearViewSubMenu: true,
- flexGap: 0,
- ignoreClick: true,
- linearViewExpandable: true,
- icon: title,
- _height: 30,
- // backgroundColor: hidden ? ComputedField.MakeFunction(hidden, { }, { _readOnly_: true }) as any : "transparent",
- linearViewIsExpanded: expanded ? !(ComputedField.MakeFunction(expanded) as any) : undefined,
- hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
- }, menuDocList));
- } else {
- docList.push(Docs.Create.FontIconDocument({
- _nativeWidth: width ? width : 25,
- _nativeHeight: 25,
- _width: width ? width : 25,
- _height: 25,
- icon,
- toolTip,
- script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined,
- btnType,
- btnList: new List<string>(list),
- ignoreClick,
- _stayInCollection: true,
- _hideContextMenu: true,
- _lockedPosition: true,
- system: true,
- dontUndo: true,
- title,
- color: Colors.WHITE,
- // backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult, {}, {_readOnly_:true}) as any : "transparent",
- _dropAction: "alias",
- hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
- _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
- onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }, { _readOnly_: false }) : undefined
- }));
- }
- });
-
- doc.contextMenuBtns = CurrentUserUtils.linearButtonList({ title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList);
- }
- }
-
- // sets up the default set of documents to be shown in the Overlay layer
- static setupOverlays(doc: Doc) {
- if (doc.myOverlayDocs === undefined) {
- doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true }));
- }
+ static contextMenuTools():Button[] {
+ return [
+ { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
+ CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn,
+ CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
+ CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
+ CollectionViewType.Grid, CollectionViewType.NoteTaking]),
+ title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+ { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+ { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
+ { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}},
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform", true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Text", icon: "text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), funcs: {hidden: 'false', linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.RTF}")`} }, // Always available
+ { title: "Ink", icon: "ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), funcs: {hidden: 'false', inearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.INK}")`} }, // Always available
+ { title: "Web", icon: "web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), funcs: {hidden: `!SelectionManager_selectedDocType("${DocumentType.WEB}")`, linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.WEB}")`, } }, // Only when Web is selected
+ { title: "Schema", icon: "schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), funcs: {hidden: `!SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`, linearViewIsExpanded: `SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected
+ ];
}
- // the initial presentation Doc to use
- static setupDefaultPresentation(doc: Doc) {
- if (doc["template-presentation"] === undefined) {
- doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
- title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _fitWidth: true, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true
- }));
+ /// initializes a context menu button for the top bar context menu
+ static setupContextMenuButton(params:Button, btnDoc?:Doc) {
+ const reqdOpts:DocumentOptions = {
+ ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
+ backgroundColor: params.scripts?.onClick ? undefined: "transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background
+ color: Colors.WHITE, system: true, dontUndo: true,
+ _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
+ _height: 30, _nativeHeight: 30,
+ _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true,
+ _dropAction: "alias", _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
+ };
+ const reqdFuncs:{[key:string]:any} = {
+ ...params.funcs,
+ backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally
}
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
}
- // Sharing sidebar is where shared documents are contained
- static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
- if (doc.myLinkDatabase === undefined) {
- let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId);
- if (!linkDocs) {
- linkDocs = new Doc(linkDatabaseId, true);
- (linkDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail;
- (linkDocs as Doc).author = Doc.CurrentUserEmail;
- (linkDocs as Doc).data = new List<Doc>([]);
- (linkDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
- }
- doc.myLinkDatabase = new PrefetchProxy(linkDocs);
- }
- // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document
- if (doc.mySharedDocs === undefined) {
- let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer");
- if (!sharedDocs) {
- sharedDocs = Docs.Create.TreeDocument([], {
- title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
- _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
- _chromeHidden: true, boxShadow: "0 0",
- explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
- }, sharingDocumentId + "outer", sharingDocumentId);
- (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
- }
- if (sharedDocs instanceof Doc) {
- Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
- const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
- const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name });
- sharedDocs.childContextMenuFilters = new List<ScriptField>([dashboardFilter!,]);
- sharedDocs.childContextMenuScripts = new List<ScriptField>([addToDashboards!,]);
- sharedDocs.childContextMenuLabels = new List<string>(["Add to Dashboards",]);
- sharedDocs.childContextMenuIcons = new List<string>(["user-plus",]);
-
+ /// Initializes all the default buttons for the top bar context menu
+ static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
+ const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 };
+ const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
+ const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => {
+ const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
+ if (!params.subMenu) {
+ return this.setupContextMenuButton(params, menuBtnDoc);
+ } else {
+ const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
+ childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true,
+ linearViewSubMenu: true, linearViewExpandable: true, };
+ const items = params.subMenu?.map(sub =>
+ this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title))
+ );
+ return DocUtils.AssignScripts(
+ DocUtils.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), undefined, params.funcs);
}
- doc.mySharedDocs = new PrefetchProxy(sharedDocs);
- }
+ });
+ return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
}
- // Import sidebar is where shared documents are contained
- static setupImportSidebar(doc: Doc) {
- if (doc.myImportDocs === undefined) {
- const newImportButton: Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true });
- doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
- title: "My Imports", _forceActive: true, buttonMenu: true, buttonMenuDoc: newImportButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
- childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
- explainer: "This is where documents that are Imported into Dash will go."
- }));
- }
+ /// collection of documents rendered in the overlay layer above all tabs and other UI
+ static setupOverlays(doc: Doc, field = "myOverlayDocs") {
+ return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", system: true });
}
- // Search sidebar is where searches within the document are performed
- static setupSearchSidebar(doc: Doc) {
- if (doc.mySearchPanel === undefined) {
- doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({
- backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true,
- childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Panel", system: true
- })) as any as Doc;
- }
+ static setupPublished(doc:Doc, field = "myPublishedDocs") {
+ return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", system: true });
}
-
- static setupClickEditorTemplates(doc: Doc) {
- if (doc["clickFuncs-child"] === undefined) {
- // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target
- const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
- "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ",
- { thisContainer: Doc.name }), {
- title: "Click to open in target", _width: 300, _height: 200,
- targetScriptKey: "onChildClick", system: true
- });
-
- const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
- "openOnRight(self.doubleClickView)",
- {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true });
-
- doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true });
- }
- // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved.
- PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
-
- if (doc.clickFuncs === undefined) {
- const onClick = Docs.Create.ScriptingDocument(undefined, {
- title: "onClick", "onClick-rawScript": "console.log('click')",
- isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200, system: true
- }, "onClick");
- const onChildClick = Docs.Create.ScriptingDocument(undefined, {
- title: "onChildClick", "onChildClick-rawScript": "console.log('child click')",
- isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200, system: true
- }, "onChildClick");
- const onDoubleClick = Docs.Create.ScriptingDocument(undefined, {
- title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')",
- isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200, system: true
- }, "onDoubleClick");
- const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, {
- title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')",
- isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200, system: true
- }, "onChildDoubleClick");
- const onCheckedClick = Docs.Create.ScriptingDocument(undefined, {
- title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)",
- "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true,
- isTemplateForField: "onCheckedClick", _width: 300, _height: 200, system: true
- }, "onCheckedClick");
- doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true });
+
+ /// The database of all links on all documents
+ static setupLinkDocs(doc: Doc, linkDatabaseId: string) {
+ if (!(Docs.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
+ const linkDocs = new Doc(linkDatabaseId, true);
+ linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
+ linkDocs.author = Doc.CurrentUserEmail;
+ linkDocs.data = new List<Doc>([]);
+ linkDocs["acl-Public"] = SharingPermissions.Augment;
+ doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
- PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
+ }
- return doc.clickFuncs as Doc;
+ /// Shared documents option on the left side button panel
+ // A user's sharing document is where all documents that are shared to that user are placed.
+ // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field
+ // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents
+ static setupSharedDocs(doc: Doc, sharingDocumentId: string) {
+ const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
+ const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name });
+ const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}";
+
+ const sharedScripts = { treeViewChildDoubleClick: dblClkScript, }
+ const sharedDocOpts:DocumentOptions = {
+ title: "My Shared Docs",
+ userColor: "rgb(202, 202, 202)",
+ childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]),
+ childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
+ childContextMenuLabels: new List<string>(["Add to Dashboards",]),
+ childContextMenuIcons: new List<string>(["user-plus",]),
+ "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
+ childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
+ // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar
+ _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
+ explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
+ };
+
+ DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts);
}
- static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
- if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
- const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
- reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]),
+ /// Import option on the left side button panel
+ static setupImportSidebar(doc: Doc, field:string) {
+ const reqdOpts:DocumentOptions = {
+ title: "My Imports", _forceActive: true, buttonMenu: true, ignoreClick: true, _showTitle: "title",
+ _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
+ childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
+ dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go."
+ };
+ const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts);
+
+ const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer",
+ _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
+ buttonText: "Import", icon: "upload", system: true };
+ DocUtils.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
+ return myImports;
+ }
+ /// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be
+ /// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field
+ /// whether to revert to "default" values, or to leave them as the user/system last set them.
+ static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
+ reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]),
async () => {
- const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
+ const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
+ SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
}, { fireImmediately: true });
- // Document properties on load
- doc.system = true;
- doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
- doc.title = Doc.CurrentUserEmail;
- doc._raiseWhenDragged = true;
- doc._showLabel = false;
- doc._showMenuLabel = true;
- doc.textAlign = StrCast(doc.textAlign, "left");
- doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
- doc.activeInkWidth = Number(StrCast(doc.activeInkWidth, "1"));
- doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
- doc.activeFillColor = StrCast(doc.activeFillColor, "");
- doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
- doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "");
- doc.activeDash = StrCast(doc.activeDash, "0");
- doc.fontSize = StrCast(doc.fontSize, "12px");
- doc.fontFamily = StrCast(doc.fontFamily, "Arial");
- doc.fontColor = StrCast(doc.fontColor, "black");
- doc.fontHighlight = StrCast(doc.fontHighlight, "");
- doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
- doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null);
- doc.noviceMode = BoolCast(doc.noviceMode, true);
- doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
- doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
- Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
- doc.savedFilters = new List<Doc>();
+ doc.system ?? (doc.system = true);
+ doc.title ?? (doc.title = Doc.CurrentUserEmail);
+ Doc.noviceMode ?? (Doc.noviceMode = true);
+ doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true);
+ doc._showLabel ?? (doc._showLabel = true);
+ doc.textAlign ?? (doc.textAlign = "left");
+ doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");;
+ doc.activeInkWidth ?? (doc.activeInkWidth = 1);
+ doc.activeInkBezier ?? (doc.activeInkBezier = "0");
+ doc.activeFillColor ?? (doc.activeFillColor = "");
+ doc.activeArrowStart ?? (doc.activeArrowStart = "");
+ doc.activeArrowEnd ?? (doc.activeArrowEnd = "");
+ doc.activeDash ?? (doc.activeDash == "0");
+ doc.fontSize ?? (doc.fontSize = "12px");
+ doc.fontFamily ?? (doc.fontFamily = "Arial");
+ doc.fontColor ?? (doc.fontColor = "black");
+ doc.fontHighlight ?? (doc.fontHighlight = "");
+ doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false);
+ doc.savedFilters ?? (doc.savedFilters = new List<Doc>());
doc.filterDocCount = 0;
doc.freezeChildren = "remove|add";
+ this.setupLinkDocs(doc, linkDatabaseId);
+ this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing
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.setupImportSidebar(doc); // sets up the import sidebar
- this.setupSearchSidebar(doc); // sets up the search sidebar
this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
- this.setupOverlays(doc); // documents in overlay layer
- this.setupContextMenuButtons(doc); // set up context menu buttons
+ this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard
+ this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
+ this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
this.setupDockedButtons(doc); // the bottom bar of font icons
- await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
- await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId);
- if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
-
- setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered
-
- // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
- doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
- doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
-
- // uncomment this to setup a default note style that uses the custom header layout
- // PromiseValue(doc.emptyHeader).then(factory => {
- // if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) {
- // const deleg = Doc.delegateDragFactory(factory as Doc);
- // deleg.title = "header";
- // doc.defaultTextLayout = new PrefetchProxy(deleg);
- // Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg);
- // }
- // });
- setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500);
- doc.fieldInfos = await Docs.setupFieldInfos();
+ this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left
+ this.setupDocTemplates(doc); // sets up the template menu of templates
+ this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
+ DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
+ DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents
+
if (doc.activeDashboard instanceof Doc) {
// undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme;
}
- if (doc.activeCollectionBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default.
- doc.activeCollectionBackground = undefined;
- }
+ new LinkManager();
+
+ setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
return doc;
}
+ static setupFieldInfos(doc:Doc, field="fieldInfos") {
+ const fieldInfoOpts = { title: "Field Infos", system: true}; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object
+ const infos = DocUtils.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts);
+ const entries = Object.entries(new DocumentOptions());
+ entries.forEach(pair => {
+ if (!Array.from(Object.keys(fieldInfoOpts)).includes(pair[0])) {
+ const options = pair[1] as FInfo;
+ const opts:DocumentOptions = { system: true, title: pair[0], ...OmitKeys(options, ["values"]).omit, fieldIsLayout: pair[0].startsWith("_")};
+ switch (options.fieldType) {
+ case "boolean": opts.fieldValues = new List<boolean>(options.values as any); break;
+ case "number": opts.fieldValues = new List<number>(options.values as any); break;
+ case Doc.name: opts.fieldValues = new List<Doc>(options.values as any); break;
+ default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
+ }
+ DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
+ }
+ });
+ }
public static async loadCurrentUser() {
return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
@@ -1333,7 +869,14 @@ export class CurrentUserUtils {
Doc.CurrentUserEmail = result.email;
resolvedPorts = JSON.parse(await (await fetch("/resolvedPorts")).text());
DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email);
- result.cacheDocumentIds && (await DocServer.GetRefFields(result.cacheDocumentIds.split(";")));
+ if (result.cacheDocumentIds)
+ {
+ const ids = result.cacheDocumentIds.split(";");
+ const batch = 10000;
+ for (let i = 0; i < ids.length; i = Math.min(ids.length, i+batch)) {
+ await DocServer.GetRefFields(ids.slice(i, i+batch));
+ }
+ }
return result;
} else {
throw new Error("There should be a user! Why does Dash think there isn't one?");
@@ -1342,20 +885,22 @@ export class CurrentUserUtils {
}
public static async loadUserDocument(id: string) {
- this.curr_id = id;
await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
- if (userDocumentId !== "guest") {
+ if (userDocumentId) {
return DocServer.GetRefField(userDocumentId).then(async field => {
Docs.newAccount = !(field instanceof Doc);
await Docs.Prototypes.initialize();
const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc;
- const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
- (await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager
- const a1 = await Cast(link?.anchor1, Doc, null);
- const a2 = await Cast(link?.anchor2, Doc, null);
- });
- return updated;
+ this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
+ if (Docs.newAccount) {
+ if (Doc.CurrentUserEmail === "guest") {
+ DashboardView.createNewDashboard(undefined, "guest dashboard");
+ } else {
+ userDoc.activePage = "home";
+ }
+ }
+ return userDoc;
});
} else {
throw new Error("There should be a user id! Why does Dash think there isn't one?");
@@ -1363,43 +908,6 @@ export class CurrentUserUtils {
});
}
- public static _urlState: HistoryUtil.DocUrl;
-
- public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => {
- CurrentUserUtils.MainDocId = doc[Id];
-
- if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard
- !("presentationView" in doc) && (doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]));
- userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc);
- }
- const state = CurrentUserUtils._urlState;
- if (state.sharing === true && !userDoc) {
- DocServer.Control.makeReadOnly();
- } else {
- fromHistory || HistoryUtil.pushState({
- type: "doc",
- docId: doc[Id],
- readonly: state.readonly,
- nro: state.nro,
- sharing: false,
- });
- if (state.readonly === true || state.readonly === null) {
- DocServer.Control.makeReadOnly();
- } else if (state.safe) {
- if (!state.nro) {
- DocServer.Control.makeReadOnly();
- }
- CollectionView.SetSafeMode(true);
- } else if (state.nro || state.nro === null || state.readonly === false) {
- } else if (doc.readOnly) {
- DocServer.Control.makeReadOnly();
- } else {
- DocServer.Control.makeEditable();
- }
- }
-
- return true;
- }
public static importDocument = () => {
const input = document.createElement("input");
@@ -1409,199 +917,36 @@ export class CurrentUserUtils {
input.onchange = async _e => {
const upload = Utils.prepend("/uploadDoc");
const formData = new FormData();
- const file = input.files && input.files[0];
- if (file && file.type === 'application/zip') {
- formData.append('file', file);
- formData.append('remap', "true");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json !== "error") {
- const doc = Docs.newAccount ? undefined : await DocServer.GetRefField(json);
- if (doc instanceof Doc) {
- setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs =>
- docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
- }
- }
+ const file = input.files?.[0];
+ if (file?.type === 'application/zip') {
+ const doc = await Doc.importDocument(file);
+ // NOT USING SOLR, so need to replace this with something else // if (doc instanceof Doc) {
+ // setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs =>
+ // docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
+ // }
+ const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
+ doc instanceof Doc && list?.splice(0, 0, doc);
} else if (input.files && input.files.length !== 0) {
- const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null);
const disposer = OverlayView.ShowSpinner();
- DocListCastAsync(importDocs.data).then(async list => {
- const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {});
- if (results.length !== input.files?.length) {
- alert("Error uploading files - possibly due to unsupported file types");
- }
- list?.splice(0, 0, ...results);
- disposer();
- });
+ const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {});
+ if (results.length !== input.files?.length) {
+ alert("Error uploading files - possibly due to unsupported file types");
+ }
+ const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
+ list?.splice(0, 0, ...results);
+ disposer();
} else {
console.log("No file selected");
}
};
input.click();
}
-
- public static async snapshotDashboard(userDoc: Doc) {
- const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
- Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy);
- CurrentUserUtils.openDashboard(userDoc, copy);
- }
-
- public static createNewDashboard = async (userDoc: Doc, id?: string) => {
- const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true);
- const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc;
- const dashboardCount = DocListCast(dashboards.data).length + 1;
- const emptyPane = Cast(userDoc.emptyPane, Doc, null);
- emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
- const freeformOptions: DocumentOptions = {
- x: 0,
- y: 400,
- _width: 1500,
- _height: 1000,
- _fitWidth: true,
- _backgroundGridShow: true,
- title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
- };
- const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
- freeformDoc.context = dashboardDoc;
-
- // switching the tabs from the datadoc to the regular doc
- const dashboardTabs = dashboardDoc[DataSym].data;
- dashboardDoc[DataSym].data = new List<Doc>();
- dashboardDoc.data = dashboardTabs;
-
- // collating all docs on the dashboard to make a data-all field
- const allDocs = new List<Doc>();
- const allDocs2 = new List<Doc>(); // Array.from, spread, splice all cause so stack or acl issues for some reason
- DocListCast(dashboardTabs).forEach(doc => {
- const tabDocs = DocListCast(doc.data);
- allDocs.push(...tabDocs);
- allDocs2.push(...tabDocs);
- });
- dashboardDoc[DataSym]["data-all"] = allDocs;
- dashboardDoc["data-all"] = allDocs2;
- DocListCast(dashboardDoc.data).forEach(doc => doc.dashboard = dashboardDoc);
- DocListCast(dashboardDoc.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
-
- userDoc.activePresentation = presentation;
-
- Doc.AddDocToList(dashboards, "data", dashboardDoc);
- CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
- }
-
- public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
- const tbox = Docs.Create.TextDocument("", {
- _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor,
- _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, title
- });
- const template = Doc.UserDoc().defaultTextLayout;
- if (template instanceof Doc) {
- tbox._width = NumCast(template._width);
- tbox.layoutKey = "layout_" + StrCast(template.title);
- Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
- }
- return tbox;
- }
-
- public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); }
- public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); }
- public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); }
- public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); }
- public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); }
- public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); }
- public static get OverlayDocs() { return DocListCast((Doc.UserDoc().myOverlayDocs as Doc)?.data); }
- public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; }
- @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; }
}
-ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) {
- const copy = Doc.copyDragFactory(dragFactory);
- if (copy) {
- CollectionDockingView.AddSplit(copy, "right");
- const view = DocumentManager.Instance.getFirstDocumentView(copy);
- view && SelectionManager.SelectView(view, false);
- }
-});
-ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); },
- "document containing all shared Docs");
-ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; },
- "is Dash in novice mode");
-ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); },
- "creates a snapshot copy of a dashboard");
-ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); },
- "creates a new dashboard when called");
-ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); },
- "creates a new presentation when called");
-ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); },
- "creates a new folder in myFiles when called");
-ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
- "returns all the links to the document or its annotations", "(doc: any)");
-ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); },
- "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function shareDashboard(dashboard: Doc) {
- SharingManager.Instance.open(undefined, dashboard);
-},
- "opens sharing dialog for Dashboard");
-ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) {
- const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data);
- if (dashboards && dashboards.length > 1) {
- if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboards.find(doc => doc !== dashboard)!);
- Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard);
- }
-},
- "Remove Dashboard from Dashboards");
-ScriptingGlobals.add(async function addToDashboards(dashboard: Doc) {
- const dashboardAlias = Doc.MakeAlias(dashboard);
-
- const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]);
-
- // moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly
- // dashboard["data-all"] = new List<Doc>(allDocs);
- // dashboardAlias["data-all"] = new List<Doc>((allDocs || []).map(doc => Doc.MakeAlias(doc)));
-
- // const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
- // dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
-
- dashboardAlias.data = new List<Doc>(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder)));
- DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias);
- //new List<Doc>();
- DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
- Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias);
- CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias);
-},
- "adds Dashboard to set of Dashboards");
-
-/**
- * Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard.
- */
-ScriptingGlobals.add(function dynamicOffScreenDocs(dashboard: Doc) {
- if (dashboard[DataSym] instanceof Doc) {
- const allDocs = DocListCast(dashboard["data-all"]);
- const onScreenTab = DocListCast(dashboard.data)[0];
- const onScreenDocs = DocListCast(onScreenTab.data);
- return new List<Doc>(allDocs.reduce((result: Doc[], doc) => {
- !onScreenDocs.includes(doc) && !onScreenDocs.includes(doc.aliasOf as Doc) && (result.push(doc));
- return result;
- }, []));
- }
- return [];
-});
-ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) {
- let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined;
- if (selected && checkParent) {
- const parentDoc: Doc = Cast(selected.context, Doc, null);
- selected = parentDoc;
- }
- if (selected && docType && selected.type === docType) return false;
- else if (selected && colType && selected.viewType === colType) return false;
- else if (selected && !colType && !docType) return false;
- else return true;
-});
-ScriptingGlobals.add(function makeTopLevelFolder() {
- const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
- TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined };
- return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder);
-});
-ScriptingGlobals.add(function toggleComicMode() {
- Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic";
-}); \ No newline at end of file
+ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
+ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
+ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called");
+ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called");
+ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)");
+ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 0a00ab6e0..d3ac2f03f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -4,64 +4,66 @@ import { Id } from '../../fields/FieldSymbols';
import { Cast } from '../../fields/Types';
import { returnFalse } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionView } from '../views/collections/CollectionView';
import { LightboxView } from '../views/LightboxView';
import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
+import { CollectionView } from '../views/collections/CollectionView';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
export class DocumentManager {
-
//global holds all of the nodes (regardless of which collection they're in)
- @observable public DocumentViews: DocumentView[] = [];
+ @observable public DocumentViews = new Set<DocumentView>();
@observable public LinkAnchorBoxViews: DocumentView[] = [];
@observable public RecordingEvent = 0;
- @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
+ @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
private static _instance: DocumentManager;
- public static get Instance(): DocumentManager { return this._instance || (this._instance = new this()); }
+ public static get Instance(): DocumentManager {
+ return this._instance || (this._instance = new this());
+ }
//private constructor so no other class can create a nodemanager
- private constructor() { }
+ private constructor() {}
@action
public AddView = (view: DocumentView) => {
//console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const viewAnchorIndex = view.props.LayoutTemplateString.includes("anchor2") ? "anchor2" : "anchor1";
+ const viewAnchorIndex = view.props.LayoutTemplateString.includes('anchor2') ? 'anchor2' : 'anchor1';
DocListCast(view.rootDoc.links).forEach(link => {
- this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).
- forEach(otherView => this.LinkedDocumentViews.push(
- {
- a: viewAnchorIndex === "anchor2" ? otherView : view,
- b: viewAnchorIndex === "anchor2" ? view : otherView,
- l: link
- })
- );
+ this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
+ this.LinkedDocumentViews.push({
+ a: viewAnchorIndex === 'anchor2' ? otherView : view,
+ b: viewAnchorIndex === 'anchor2' ? view : otherView,
+ l: link,
+ })
+ );
});
this.LinkAnchorBoxViews.push(view);
// this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
// view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
} else {
- this.DocumentViews.push(view);
+ this.DocumentViews.add(view);
}
- }
+ };
public RemoveView = action((view: DocumentView) => {
- this.LinkedDocumentViews.slice().forEach(action(pair => {
- if (pair.a === view || pair.b === view) {
- const li = this.LinkedDocumentViews.indexOf(pair);
- li !== -1 && this.LinkedDocumentViews.splice(li, 1);
- }
- }));
+ this.LinkedDocumentViews.slice().forEach(
+ action(pair => {
+ if (pair.a === view || pair.b === view) {
+ const li = this.LinkedDocumentViews.indexOf(pair);
+ li !== -1 && this.LinkedDocumentViews.splice(li, 1);
+ }
+ })
+ );
if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const index = this.LinkAnchorBoxViews.indexOf(view);
this.LinkAnchorBoxViews.splice(index, 1);
} else {
- const index = this.DocumentViews.indexOf(view);
- index !== -1 && this.DocumentViews.splice(index, 1);
+ this.DocumentViews.delete(view);
}
SelectionManager.DeselectView(view);
});
@@ -69,13 +71,13 @@ export class DocumentManager {
//gets all views
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
if (view.rootDoc[Id] === id) {
toReturn.push(view);
}
});
if (toReturn.length === 0) {
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
const doc = view.rootDoc.proto;
if (doc && doc[Id] && doc[Id] === id) {
toReturn.push(view);
@@ -95,14 +97,14 @@ export class DocumentManager {
const passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
for (const pass of passes) {
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
return;
}
});
if (!toReturn) {
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
const doc = view.rootDoc.proto;
if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
@@ -121,19 +123,18 @@ export class DocumentManager {
}
public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
- const docViews = DocumentManager.Instance.DocumentViews;
const views: DocumentView[] = [];
- docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
- }
+ };
public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc);
return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
- }
+ };
public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
- const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
- const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
+ const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
+ const lightViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
// heuristic to return the "best" documents first:
// choose a document in the lightbox first
@@ -146,17 +147,16 @@ export class DocumentManager {
return toReturn;
}
-
static addView = (doc: Doc, finished?: () => void) => {
- CollectionDockingView.AddSplit(doc, "right");
+ CollectionDockingView.AddSplit(doc, 'right');
finished?.();
- }
+ };
public jumpToDocument = async (
- targetDoc: Doc, // document to display
- willZoom: boolean, // whether to zoom doc to take up most of screen
+ targetDoc: Doc, // document to display
+ willZoom: boolean, // whether to zoom doc to take up most of screen
createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
- docContext?: Doc, // context to load that should contain the target
- linkDoc?: Doc, // link that's being followed
+ docContext: Doc[], // context to load that should contain the target
+ linkDoc?: Doc, // link that's being followed
closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
finished?: () => void,
@@ -167,101 +167,158 @@ export class DocumentManager {
originalTarget = originalTarget ?? targetDoc;
const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
const docView = getFirstDocView(targetDoc, originatingDoc);
- const wasHidden = targetDoc.hidden; //
- if (wasHidden) runInAction(() => targetDoc.hidden = false); // if the target is hidden, un-hide it here.
+ const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
+ const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field
+ var wasHidden = resolvedTarget.hidden;
+ if (wasHidden) {
+ runInAction(() => {
+ resolvedTarget.hidden = false; // if the target is hidden, un-hide it here.
+ docView?.props.bringToFront(resolvedTarget);
+ });
+ }
const focusAndFinish = (didFocus: boolean) => {
+ const finalTargetDoc = resolvedTarget;
if (originatingDoc?.isPushpin) {
- if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
- targetDoc.hidden = !targetDoc.hidden;
+ if (!didFocus && !wasHidden) {
+ // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
+ finalTargetDoc.hidden = !finalTargetDoc.hidden;
}
} else {
- targetDoc.hidden && (targetDoc.hidden = undefined);
+ finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined);
!noSelect && docView?.select(false);
}
finished?.();
- return false;
};
- const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
- const annoContainerView = annotatedDoc && getFirstDocView(annotatedDoc);
- const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
- const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
+ const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc);
+ const contextDocs = docContext.length ? await DocListCastAsync(docContext[0].data) : undefined;
+ const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined;
const targetDocContext = contextDoc || annotatedDoc;
- const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) ||
- (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
+ const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
- if (!docView && annoContainerView) {
- annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+ if (annoContainerView) {
+ if (annoContainerView.props.Document.layoutKey === 'layout_icon') {
+ annoContainerView.iconify(() =>
+ annoContainerView.focus(targetDoc, {
+ originalTarget,
+ willZoom,
+ scale: presZoom,
+ afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ focusAndFinish(true);
+ res(ViewAdjustment.doNothing);
+ }),
+ })
+ );
+ return;
+ } else if (!docView && targetDoc.type !== DocumentType.MARKER) {
+ annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+ }
}
if (focusView) {
!noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox
- focusView.focus(targetDoc, {
- originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => {
- focusAndFinish(didFocus);
- res(ViewAdjustment.doNothing);
- })
- });
+ const doFocus = (forceDidFocus: boolean) =>
+ focusView.focus(originalTarget ?? targetDoc, {
+ originalTarget,
+ willZoom,
+ scale: presZoom,
+ afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ focusAndFinish(forceDidFocus || didFocus);
+ res(ViewAdjustment.doNothing);
+ }),
+ });
+ if (focusView.props.Document.layoutKey === 'layout_icon') {
+ focusView.iconify(() => doFocus(true));
+ } else {
+ doFocus(false);
+ }
} else {
- if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
+ if (!targetDocContext) {
+ // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
- } else { // otherwise try to get a view of the context of the target
- if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
- targetDocContext._viewTransition = "transform 500ms";
+ } else {
+ // otherwise try to get a view of the context of the target
+ if (targetDocContextView) {
+ // we found a context view and aren't forced to create a new one ... focus on the context first..
+ wasHidden = wasHidden || targetDocContextView.rootDoc.hidden;
+ targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden
+ targetDocContext._viewTransition = 'transform 500ms';
targetDocContextView.props.focus(targetDocContextView.rootDoc, {
- willZoom, afterFocus: async () => {
+ willZoom,
+ afterFocus: async () => {
targetDocContext._viewTransition = undefined;
+ if (targetDocContext.layoutKey === 'layout_icon') {
+ targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom));
+ }
return ViewAdjustment.doNothing;
- }
+ },
});
// now find the target document within the context
- if (targetDoc._timecodeToShow) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
+ if (targetDoc._timecodeToShow) {
+ // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow;
finished?.();
- } else { // no timecode means we need to find the context view and focus on our target
+ } else {
+ // no timecode means we need to find the context view and focus on our target
const findView = (delay: number) => {
- const retryDocView = getFirstDocView(targetDoc); // test again for the target view snce we presumably created the context above by focusing on it
- if (retryDocView) { // we found the target in the context
+ const retryDocView = getFirstDocView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it
+ if (retryDocView) {
+ // we found the target in the context.
Doc.linkFollowHighlight(retryDocView.rootDoc);
- retryDocView.props.focus(targetDoc, {
- willZoom, afterFocus: (didFocus: boolean) =>
+ retryDocView.focus(targetDoc, {
+ willZoom,
+ afterFocus: (didFocus: boolean) =>
new Promise<ViewAdjustment>(res => {
- !noSelect && focusAndFinish(didFocus);
+ !noSelect && focusAndFinish(true);
res(ViewAdjustment.doNothing);
- })
+ }),
}); // focus on the target in the context
- } else if (delay > 1500) {
+ } else if (delay > 1000) {
// we didn't find the target, so it must have moved out of the context. Go back to just creating it.
if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc);
- if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document
+ if (targetDoc.layout) {
+ // there will no layout for a TEXTANCHOR type document
createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
}
} else {
- setTimeout(() => findView(delay + 250), 250);
+ setTimeout(() => findView(delay + 200), 200);
}
};
- findView(0);
+ setTimeout(() => findView(0), 0);
+ }
+ } else {
+ if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') {
+ const docContextView = this.getFirstDocumentView(docContext[0]);
+ if (docContextView) {
+ return docContextView.iconify(() =>
+ this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom)
+ );
+ }
}
- } else { // there's no context view so we need to create one first and try again when that finishes
+ // there's no context view so we need to create one first and try again when that finishes
const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget);
- createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
- finishFunc);
+ createViewFunc(
+ targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
+ finishFunc
+ );
}
}
}
- }
-
+ };
}
-ScriptingGlobals.add(function DocFocusOrOpen(doc: any) {
- const dv = DocumentManager.Instance.getDocumentView(doc);
- if (dv && dv.props.Document === doc) {
- dv.props.focus(doc, { willZoom: true });
+export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
+ const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
+ const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView);
+ if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) {
+ dv.props.focus(dv.props.Document, { willZoom: true });
Doc.linkFollowHighlight(dv?.props.Document, false);
- }
- else {
- const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null);
+ } else {
+ const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null);
const showDoc = context || doc;
- CollectionDockingView.AddSplit(showDoc === Doc.GetProto(showDoc) ? Doc.MakeAlias(showDoc) : showDoc, "right") && context &&
- setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc));
+ const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc;
+
+ CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc));
}
-}); \ No newline at end of file
+}
+ScriptingGlobals.add(DocFocusOrOpen);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c9c499fff..947882958 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,41 +1,35 @@
-import { action } from "mobx";
-import { DateField } from "../../fields/DateField";
-import { Doc, Field, Opt } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { PrefetchProxy } from "../../fields/Proxy";
-import { listSpec } from "../../fields/Schema";
-import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
-import { ScriptField } from "../../fields/ScriptField";
-import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types";
-import { emptyFunction } from "../../Utils";
-import { Docs, DocUtils } from "../documents/Documents";
-import * as globalCssVariables from "../views/global/globalCssVariables.scss";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { SnappingManager } from "./SnappingManager";
-import { UndoManager } from "./UndoManager";
-
-export type dropActionType = "alias" | "copy" | "move" | "same" | "proto" | "none" | undefined; // undefined = move, "same" = move but don't call removeDropProperties
+import { action, observable, runInAction } from 'mobx';
+import { DateField } from '../../fields/DateField';
+import { Doc, Field, Opt } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { PrefetchProxy } from '../../fields/Proxy';
+import { listSpec } from '../../fields/Schema';
+import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
+import { ScriptField } from '../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { emptyFunction, Utils } from '../../Utils';
+import { Docs, DocUtils } from '../documents/Documents';
+import * as globalCssVariables from '../views/global/globalCssVariables.scss';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { SnappingManager } from './SnappingManager';
+import { UndoManager } from './UndoManager';
+
+export type dropActionType = 'alias' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call removeDropProperties
/**
* Initialize drag
- * @param _reference: The HTMLElement that is being dragged
+ * @param _reference: The HTMLElement that is being dragged
* @param docFunc: The Dash document being moved
* @param moveFunc: The function called when the document is moved
* @param dropAction: What to do with the document when it is dropped
* @param dragStarted: Method to call when the drag is started
*/
-export function SetupDrag(
- _reference: React.RefObject<HTMLElement>,
- docFunc: () => Doc | Promise<Doc> | undefined,
- moveFunc?: DragManager.MoveFunction,
- dropAction?: dropActionType,
- dragStarted?: () => void
-) {
+export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc> | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, dragStarted?: () => void) {
const onRowMove = async (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
const doc = await docFunc();
if (doc) {
@@ -47,7 +41,7 @@ export function SetupDrag(
}
};
const onRowUp = (): void => {
- document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
};
const onItemDown = async (e: React.PointerEvent) => {
@@ -56,15 +50,10 @@ export function SetupDrag(
if (e.shiftKey) {
e.persist();
const dragDoc = await docFunc();
- dragDoc && DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, [dragDoc]);
+ dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]);
} else {
- document.addEventListener("pointermove", onRowMove);
- document.addEventListener("pointerup", onRowUp);
+ document.addEventListener('pointermove', onRowMove);
+ document.addEventListener('pointerup', onRowUp);
}
}
};
@@ -74,12 +63,19 @@ export function SetupDrag(
export namespace DragManager {
let dragDiv: HTMLDivElement;
let dragLabel: HTMLDivElement;
- export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
+ export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void>;
+ export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
+ export function GetRaiseWhenDragged() {
+ return BoolCast(Doc.UserDoc()._raiseWhenDragged);
+ }
+ export function SetRaiseWhenDragged(val: boolean) {
+ Doc.UserDoc()._raiseWhenDragged = val;
+ }
export function Root() {
- const root = document.getElementById("root");
+ const root = document.getElementById('root');
if (!root) {
- throw new Error("No root element found");
+ throw new Error('No root element found');
}
return root;
}
@@ -87,27 +83,20 @@ export namespace DragManager {
export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
export type RemoveFunction = (document: Doc | Doc[]) => boolean;
- export interface DragDropDisposer { (): void; }
+ export interface DragDropDisposer {
+ (): void;
+ }
export interface DragOptions {
dragComplete?: (e: DragCompleteEvent) => void; // function to invoke when drag has completed
- hideSource?: boolean; // hide source document during drag
- offsetX?: number; // offset of top left of source drag visual from cursor
+ hideSource?: boolean; // hide source document during drag
+ offsetX?: number; // offset of top left of source drag visual from cursor
offsetY?: number;
noAutoscroll?: boolean;
}
// event called when the drag operation results in a drop action
export class DropEvent {
- constructor(
- readonly x: number,
- readonly y: number,
- readonly complete: DragCompleteEvent,
- readonly shiftKey: boolean,
- readonly altKey: boolean,
- readonly metaKey: boolean,
- readonly ctrlKey: boolean,
- readonly embedKey: boolean,
- ) { }
+ constructor(readonly x: number, readonly y: number, readonly complete: DragCompleteEvent, readonly shiftKey: boolean, readonly altKey: boolean, readonly metaKey: boolean, readonly ctrlKey: boolean, readonly embedKey: boolean) {}
}
// event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
@@ -139,20 +128,22 @@ export namespace DragManager {
treeViewDoc?: Doc;
offset: number[];
canEmbed?: boolean;
- userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys
- defaultDropAction?: dropActionType; // an optionally specified default drop action when there is no user drop actionl - this will be honored if there is no user drop action
- dropAction: dropActionType; // a drop action request by the initiating code. the actual drop action may be different -- eg, if the request is 'alias', but the document is dropped within the same collection, the drop action will be switched to 'move'
+ userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys
+ defaultDropAction?: dropActionType; // an optionally specified default drop action when there is no user drop actionl - this will be honored if there is no user drop action
+ dropAction: dropActionType; // a drop action request by the initiating code. the actual drop action may be different -- eg, if the request is 'alias', but the document is dropped within the same collection, the drop action will be switched to 'move'
removeDropProperties?: string[];
moveDocument?: MoveFunction;
removeDocument?: RemoveFunction;
isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
}
export class LinkDragData {
- constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc,) {
+ constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc) {
this.linkDragView = dragView;
this.linkSourceGetAnchor = linkSourceGetAnchor;
}
- get dragDocument() { return this.linkDragView.props.Document; }
+ get dragDocument() {
+ return this.linkDragView.props.Document;
+ }
linkSourceGetAnchor: () => Doc;
linkSourceDoc?: Doc;
linkDragView: DocumentView;
@@ -179,43 +170,29 @@ export namespace DragManager {
userDropAction: dropActionType;
}
- export function MakeDropTarget(
- element: HTMLElement,
- dropFunc: (e: Event, de: DropEvent) => void,
- doc?: Doc,
- preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void,
- ): DragDropDisposer {
- if ("canDrop" in element.dataset) {
- throw new Error(
- "Element is already droppable, can't make it droppable again"
- );
+ export function MakeDropTarget(element: HTMLElement, dropFunc: (e: Event, de: DropEvent) => void, doc?: Doc, preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void): DragDropDisposer {
+ if ('canDrop' in element.dataset) {
+ throw new Error("Element is already droppable, can't make it droppable again");
}
- element.dataset.canDrop = "true";
+ element.dataset.canDrop = 'true';
const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
const preDropHandler = (e: Event) => {
const de = (e as CustomEvent<DropEvent>).detail;
preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType);
};
- element.addEventListener("dashOnDrop", handler);
- doc && element.addEventListener("dashPreDrop", preDropHandler);
+ element.addEventListener('dashOnDrop', handler);
+ doc && element.addEventListener('dashPreDrop', preDropHandler);
return () => {
- element.removeEventListener("dashOnDrop", handler);
- doc && element.removeEventListener("dashPreDrop", preDropHandler);
+ element.removeEventListener('dashOnDrop', handler);
+ doc && element.removeEventListener('dashPreDrop', preDropHandler);
delete element.dataset.canDrop;
};
}
// drag a document and drop it (or make an alias/copy on drop)
- export function StartDocumentDrag(
- eles: HTMLElement[],
- dragData: DocumentDragData,
- downX: number,
- downY: number,
- options?: DragOptions,
- dropEvent?: () => any
- ) {
+ export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, dropEvent?: () => any) {
const addAudioTag = (dropDoc: any) => {
- dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
+ dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField());
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
return dropDoc;
};
@@ -224,16 +201,27 @@ export namespace DragManager {
dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
- docDragData.droppedDocuments =
- await Promise.all(dragData.draggedDocuments.map(async d => !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
- docDragData.dropAction === "alias" ? Doc.MakeAlias(d) :
- docDragData.dropAction === "proto" ? Doc.GetProto(d) :
- docDragData.dropAction === "copy" ? (await Doc.MakeClone(d)).clone : d));
- !["same", "proto"].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
- const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []);
- const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
- remProps.map(prop => drop[prop] = undefined);
- });
+ docDragData.droppedDocuments = await Promise.all(
+ dragData.draggedDocuments.map(async d =>
+ !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
+ ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
+ : docDragData.dropAction === 'alias'
+ ? Doc.MakeAlias(d)
+ : docDragData.dropAction === 'proto'
+ ? Doc.GetProto(d)
+ : docDragData.dropAction === 'copy'
+ ? (
+ await Doc.MakeClone(d)
+ ).clone
+ : d
+ )
+ );
+ !['same', 'proto'].includes(docDragData.dropAction as any) &&
+ docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
+ const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec('string'), []);
+ const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
+ remProps.map(prop => (drop[prop] = undefined));
+ });
}
return e;
};
@@ -242,23 +230,22 @@ export namespace DragManager {
return true;
}
- // drag a button template and drop a new button
- export function
- StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+ // drag a button template and drop a new button
+ export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
initialize?.(bd);
- Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List<string>(params);
+ Doc.GetProto(bd)['onClick-paramFieldKeys'] = new List<string>(params);
e.docDragData && (e.docDragData.droppedDocuments = [bd]);
return e;
};
options = options ?? {};
- options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate
+ options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
}
- // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption
+ // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption
export function StartAnchorAnnoDrag(eles: HTMLElement[], dragData: AnchorAnnoDragData, downX: number, downY: number, options?: DragOptions) {
StartDrag(eles, dragData, downX, downY, options);
}
@@ -281,12 +268,12 @@ export namespace DragManager {
SnappingManager.setSnapLines(horizLines, vertLines);
}
export function snapDragAspect(dragPt: number[], snapAspect: number) {
- let closest = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ let closest = Utils.SNAP_THRESHOLD;
let near = dragPt;
const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
- const denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
- if (denominator === 0) return undefined; // Lines are parallel
+ const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
+ if (denominator === 0) return undefined; // Lines are parallel
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
// let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
@@ -314,14 +301,14 @@ export namespace DragManager {
});
return { x: near[0], y: near[1] };
}
- // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
+ // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
- const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ const snapThreshold = Utils.SNAP_THRESHOLD;
const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
if (snapLines.length) {
- const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
+ const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
const rangePts = [drag - offs[0], drag - offs[1], drag - offs[2]]; // left, mid, right or top, mid, bottom pts to try to snap to snaplines
- const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest));
+ const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => (Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest)));
const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i]));
const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2;
return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag;
@@ -330,61 +317,67 @@ export namespace DragManager {
};
return {
x: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()),
- y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines())
+ y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()),
};
}
- export let docsBeingDragged: Doc[] = [];
+ export let docsBeingDragged: Doc[] = observable([] as Doc[]);
export let CanEmbed = false;
export let DocDragData: DocumentDragData | undefined;
export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
- if (dragData.dropAction === "none") return;
+ if (dragData.dropAction === 'none') return;
DocDragData = dragData instanceof DocumentDragData ? dragData : undefined;
- const batch = UndoManager.StartBatch("dragging");
+ const batch = UndoManager.StartBatch('dragging');
eles = eles.filter(e => e);
CanEmbed = dragData.canEmbed || false;
if (!dragDiv) {
- dragDiv = document.createElement("div");
- dragDiv.className = "dragManager-dragDiv";
- dragDiv.style.pointerEvents = "none";
- dragLabel = document.createElement("div");
- dragLabel.className = "dragManager-dragLabel";
- dragLabel.style.zIndex = "100001";
- dragLabel.style.fontSize = "10px";
- dragLabel.style.position = "absolute";
- dragLabel.innerText = "drag titlebar to embed on drop"; // bcz: need to move this to a status bar
+ dragDiv = document.createElement('div');
+ dragDiv.className = 'dragManager-dragDiv';
+ dragDiv.style.pointerEvents = 'none';
+ dragLabel = document.createElement('div');
+ dragLabel.className = 'dragManager-dragLabel';
+ dragLabel.style.zIndex = '100001';
+ dragLabel.style.fontSize = '10px';
+ dragLabel.style.position = 'absolute';
+ dragLabel.innerText = 'drag titlebar to embed on drop'; // bcz: need to move this to a status bar
dragDiv.appendChild(dragLabel);
DragManager.Root().appendChild(dragDiv);
}
- Object.assign(dragDiv.style, { width: "", height: "", overflow: "" });
+ Object.assign(dragDiv.style, { width: '', height: '', overflow: '' });
dragDiv.hidden = false;
- const scaleXs: number[] = [], scaleYs: number[] = [], xs: number[] = [], ys: number[] = [];
+ const scaleXs: number[] = [],
+ scaleYs: number[] = [],
+ xs: number[] = [],
+ ys: number[] = [];
- docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
const elesCont = {
- left: Number.MAX_SAFE_INTEGER, right: Number.MIN_SAFE_INTEGER,
- top: Number.MAX_SAFE_INTEGER, bottom: Number.MIN_SAFE_INTEGER
+ left: Number.MAX_SAFE_INTEGER,
+ right: Number.MIN_SAFE_INTEGER,
+ top: Number.MAX_SAFE_INTEGER,
+ bottom: Number.MIN_SAFE_INTEGER,
};
+ const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
const dragElements = eles.map(ele => {
if (!ele.parentNode) dragDiv.appendChild(ele);
- const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement;
+ const dragElement = ele.parentNode === dragDiv ? ele : (ele.cloneNode(true) as HTMLElement);
const children = Array.from(dragElement.children);
- while (children.length) { // need to replace all the maker node reference ids with new unique ids. otherwise, the clone nodes will reference the original nodes which are all hidden during the drag
+ while (children.length) {
+ // need to replace all the maker node reference ids with new unique ids. otherwise, the clone nodes will reference the original nodes which are all hidden during the drag
const next = children.pop();
next && children.push(...Array.from(next.children));
if (next) {
- ["marker-start", "marker-mid", "marker-end"].forEach(field => {
- if (next.localName.startsWith("path") && (next.attributes as any)[field]) {
- next.setAttribute(field, (next.attributes as any)[field].value.replace("#", "#X"));
+ ['marker-start', 'marker-mid', 'marker-end'].forEach(field => {
+ if (next.localName.startsWith('path') && (next.attributes as any)[field]) {
+ next.setAttribute(field, (next.attributes as any)[field].value.replace('#', '#X'));
}
});
- if (next.localName.startsWith("marker")) {
- next.id = "X" + next.id;
+ if (next.localName.startsWith('marker')) {
+ next.id = 'X' + next.id;
}
}
}
const rect = ele.getBoundingClientRect();
- const scaleX = rect.width / ele.offsetWidth;
- const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX;
+ const scaleX = rect.width / (ele.offsetWidth || rect.width);
+ const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX;
elesCont.left = Math.min(rect.left, elesCont.left);
elesCont.top = Math.min(rect.top, elesCont.top);
@@ -395,20 +388,31 @@ export namespace DragManager {
scaleXs.push(scaleX);
scaleYs.push(scaleY);
Object.assign(dragElement.style, {
- opacity: "0.7", position: "absolute", margin: "0", top: "0", bottom: "", left: "0", color: "black", transition: "none",
- borderRadius: getComputedStyle(ele).borderRadius, zIndex: globalCssVariables.contextMenuZindex,
- transformOrigin: "0 0", width: `${rect.width / scaleX}px`, height: `${rect.height / scaleY}px`,
+ opacity: '0.7',
+ position: 'absolute',
+ margin: '0',
+ top: '0',
+ bottom: '',
+ left: '0',
+ color: 'black',
+ transition: 'none',
+ borderRadius: getComputedStyle(ele).borderRadius,
+ zIndex: globalCssVariables.contextMenuZindex,
+ transformOrigin: '0 0',
+ width: `${rect.width / scaleX}px`,
+ height: `${rect.height / scaleY}px`,
transform: `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`,
});
dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`;
- if (docsBeingDragged.length) {
- const pdfBox = dragElement.getElementsByTagName("canvas");
- const pdfBoxSrc = ele.getElementsByTagName("canvas");
- Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
+ if (docsToDrag.length) {
+ const pdfBox = dragElement.getElementsByTagName('canvas');
+ const pdfBoxSrc = ele.getElementsByTagName('canvas');
+ Array.from(pdfBox)
+ .filter(pb => pb.width && pb.height)
+ .map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
}
- [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele =>
- ele.hasAttribute("style") && ((ele as any).style.pointerEvents = "none"));
+ [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none'));
dragDiv.appendChild(dragElement);
if (dragElement !== ele) {
@@ -425,10 +429,12 @@ export namespace DragManager {
return dragElement;
});
+ runInAction(() => docsBeingDragged.push(...docsToDrag));
+
const hideDragShowOriginalElements = (hide: boolean) => {
- dragLabel.style.display = hide ? "" : "none";
+ dragLabel.style.display = hide ? '' : 'none';
!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
- eles.forEach(ele => ele.hidden = hide);
+ eles.forEach(ele => (ele.hidden = hide));
};
options?.hideSource && hideDragShowOriginalElements(true);
@@ -442,35 +448,41 @@ export namespace DragManager {
AbortDrag = () => {
options?.dragComplete?.(new DragCompleteEvent(true, dragData));
- cleanupDrag();
+ cleanupDrag(true);
};
- const cleanupDrag = action(() => {
+ const cleanupDrag = action((undo: boolean) => {
hideDragShowOriginalElements(false);
- document.removeEventListener("pointermove", moveHandler, true);
- document.removeEventListener("pointerup", upHandler);
+ document.removeEventListener('pointermove', moveHandler, true);
+ document.removeEventListener('pointerup', upHandler, true);
SnappingManager.SetIsDragging(false);
SnappingManager.clearSnapLines();
- batch.end();
+ if (undo && batch.end()) UndoManager.Undo();
+ docsBeingDragged.length = 0;
});
- const moveHandler = async (e: PointerEvent) => {
+ var startWindowDragTimer: any;
+ const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
- dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction;
+ dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.ctrlKey ? 'alias' : dragData.defaultDropAction;
}
- if (e?.shiftKey && dragData.draggedDocuments.length === 1) {
- dragData.dropAction = dragData.userDropAction || "same";
- if (dragData.dropAction === "move") {
- dragData.removeDocument?.(dragData.draggedDocuments[0]);
+ if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header' || e?.shiftKey) && dragData.draggedDocuments.length === 1) {
+ if (!startWindowDragTimer) {
+ startWindowDragTimer = setTimeout(async () => {
+ startWindowDragTimer = undefined;
+ dragData.dropAction = dragData.userDropAction || 'same';
+ AbortDrag();
+ await finishDrag?.(new DragCompleteEvent(true, dragData));
+ DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, aborted => {
+ if (!aborted && (dragData.dropAction === 'move' || dragData.dropAction === 'same')) {
+ dragData.removeDocument?.(dragData.draggedDocuments[0]);
+ }
+ });
+ }, 500);
}
- AbortDrag();
- await finishDrag?.(new DragCompleteEvent(true, dragData));
- DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, dragData.droppedDocuments);
+ } else {
+ clearTimeout(startWindowDragTimer);
+ startWindowDragTimer = undefined;
}
const target = document.elementFromPoint(e.x, e.y);
@@ -478,7 +490,7 @@ export namespace DragManager {
if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) {
const autoScrollHandler = () => {
target.dispatchEvent(
- new CustomEvent<React.DragEvent>("dashDragAutoScroll", {
+ new CustomEvent<React.DragEvent>('dashDragAutoScroll', {
bubbles: true,
detail: {
shiftKey: e.shiftKey,
@@ -487,7 +499,7 @@ export namespace DragManager {
ctrlKey: e.ctrlKey,
clientX: e.clientX,
clientY: e.clientY,
- dataTransfer: new DataTransfer,
+ dataTransfer: new DataTransfer(),
button: e.button,
buttons: e.buttons,
getModifierState: e.getModifierState,
@@ -499,8 +511,8 @@ export namespace DragManager {
screenX: e.screenX,
screenY: e.screenY,
detail: e.detail,
- view: e.view ? e.view : new Window as any,
- nativeEvent: new DragEvent("dashDragAutoScroll"),
+ view: e.view ? e.view : (new Window() as any),
+ nativeEvent: new DragEvent('dashDragAutoScroll'),
currentTarget: target,
target: target,
bubbles: true,
@@ -508,14 +520,14 @@ export namespace DragManager {
defaultPrevented: true,
eventPhase: e.eventPhase,
isTrusted: true,
- preventDefault: () => "not implemented for this event" ? false : false,
- isDefaultPrevented: () => "not implemented for this event" ? false : false,
- stopPropagation: () => "not implemented for this event" ? false : false,
- isPropagationStopped: () => "not implemented for this event" ? false : false,
+ preventDefault: () => ('not implemented for this event' ? false : false),
+ isDefaultPrevented: () => ('not implemented for this event' ? false : false),
+ stopPropagation: () => ('not implemented for this event' ? false : false),
+ isPropagationStopped: () => ('not implemented for this event' ? false : false),
persist: emptyFunction,
timeStamp: e.timeStamp,
- type: "dashDragAutoScroll"
- }
+ type: 'dashDragAutoScroll',
+ },
})
);
@@ -531,18 +543,18 @@ export namespace DragManager {
lastPt = { x, y };
dragLabel.style.transform = `translate(${xs[0] + moveVec.x + (options?.offsetX || 0)}px, ${ys[0] + moveVec.y + (options?.offsetY || 0) - 20}px)`;
- dragElements.map((dragElement, i) => (dragElement.style.transform =
- `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
- );
+ dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`));
};
const upHandler = (e: PointerEvent) => {
- dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, cleanupDrag);
+ clearTimeout(startWindowDragTimer);
+ startWindowDragTimer = undefined;
+ dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false));
};
- document.addEventListener("pointermove", moveHandler, true);
- document.addEventListener("pointerup", upHandler);
+ document.addEventListener('pointermove', moveHandler, true);
+ document.addEventListener('pointerup', upHandler, true);
}
- async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
+ async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
const dropArgs = {
bubbles: true,
detail: {
@@ -552,13 +564,13 @@ export namespace DragManager {
altKey: e.altKey,
metaKey: e.metaKey,
ctrlKey: e.ctrlKey,
- embedKey: CanEmbed
- }
+ embedKey: CanEmbed,
+ },
};
- target.dispatchEvent(new CustomEvent<DropEvent>("dashPreDrop", dropArgs));
+ target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
await finishDrag?.(complete);
- target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", dropArgs));
+ target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs));
options?.dragComplete?.(complete);
endDrag?.();
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 082b6d8bd..256ab5c44 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -10,12 +10,17 @@ import { ImageField } from "../../fields/URLField";
import { ScriptingGlobals } from "./ScriptingGlobals";
import { listSpec } from "../../fields/Schema";
+export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = "") {
+ if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated
+ doc.isTemplateDoc = makeTemplate(doc, first, rename);
+ return doc;
+}
//
// converts 'doc' into a template that can be used to render other documents.
// the title of doc is used to determine which field is being templated, so
// passing a value for 'rename' allows the doc to be given a meangingful name
// after it has been converted to
-export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined): boolean {
+function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined): boolean {
const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
if (layoutDoc.layout instanceof Doc) { // its already a template
return true;
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 62d656c5d..c8b784390 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,19 +1,20 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import Select from 'react-select';
-import * as RequestPromise from "request-promise";
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
-import { StrCast, Cast } from "../../fields/Types";
-import { Utils } from "../../Utils";
-import { MainViewModal } from "../views/MainViewModal";
-import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import "./GroupManager.scss";
-import { GroupMemberView } from "./GroupMemberView";
-import { SharingManager, User } from "./SharingManager";
-import { listSpec } from "../../fields/Schema";
-import { DateField } from "../../fields/DateField";
+import * as RequestPromise from 'request-promise';
+import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
+import { StrCast, Cast } from '../../fields/Types';
+import { Utils } from '../../Utils';
+import { MainViewModal } from '../views/MainViewModal';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import './GroupManager.scss';
+import { GroupMemberView } from './GroupMemberView';
+import { SharingManager, User } from './SharingManager';
+import { listSpec } from '../../fields/Schema';
+import { DateField } from '../../fields/DateField';
+import { Id } from '../../fields/FieldSymbols';
/**
* Interface for options for the react-select component
@@ -25,7 +26,6 @@ export interface UserOptions {
@observer
export class GroupManager extends React.Component<{}> {
-
static Instance: GroupManager;
@observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
@observable private users: string[] = []; // list of users populated from the database.
@@ -34,24 +34,28 @@ export class GroupManager extends React.Component<{}> {
@observable private createGroupModalOpen: boolean = false;
private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button
- @observable private buttonColour: "#979797" | "black" = "#979797";
- @observable private groupSort: "ascending" | "descending" | "none" = "none";
+ @observable private buttonColour: '#979797' | 'black' = '#979797';
+ @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none';
constructor(props: Readonly<{}>) {
super(props);
GroupManager.Instance = this;
}
- componentDidMount() { this.populateUsers(); }
+ componentDidMount() {
+ this.populateUsers();
+ }
/**
* Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
- const raw = JSON.parse(userList) as User[];
- raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
- }
+ if (Doc.UserDoc()[Id] !== '__guest__') {
+ const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+ const raw = JSON.parse(userList) as User[];
+ raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
+ }
+ };
/**
* @returns the options to be rendered in the dropdown menu to add users and create a group.
@@ -68,11 +72,11 @@ export class GroupManager extends React.Component<{}> {
// SelectionManager.DeselectAll();
this.isOpen = true;
this.populateUsers();
- }
+ };
/**
* Hides the GroupManager.
- */
+ */
@action
close = () => {
this.isOpen = false;
@@ -81,26 +85,32 @@ export class GroupManager extends React.Component<{}> {
// this.users = [];
this.createGroupModalOpen = false;
TaskCompletionBox.taskCompleted = false;
- }
+ };
/**
* @returns the database of groups.
*/
- @computed get GroupManagerDoc(): Doc | undefined { return Doc.UserDoc().globalGroupDatabase as Doc; }
+ @computed get GroupManagerDoc(): Doc | undefined {
+ return Doc.UserDoc().globalGroupDatabase as Doc;
+ }
/**
* @returns a list of all group documents.
*/
- @computed get allGroups(): Doc[] { return DocListCast(this.GroupManagerDoc?.data); }
+ @computed get allGroups(): Doc[] {
+ return DocListCast(this.GroupManagerDoc?.data);
+ }
/**
* @returns the members of the admin group.
*/
- @computed get adminGroupMembers(): string[] { return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; }
+ @computed get adminGroupMembers(): string[] {
+ return this.getGroup('Admin') ? JSON.parse(StrCast(this.getGroup('Admin')!.members)) : '';
+ }
/**
* @returns a group document based on the group name.
- * @param groupName
+ * @param groupName
*/
getGroup(groupName: string): Doc | undefined {
return this.allGroups.find(group => group.title === groupName);
@@ -114,10 +124,9 @@ export class GroupManager extends React.Component<{}> {
return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[];
}
-
/**
* @returns a boolean indicating whether the current user has access to edit group documents.
- * @param groupDoc
+ * @param groupDoc
*/
hasEditAccess(groupDoc: Doc): boolean {
if (!groupDoc) return false;
@@ -127,12 +136,12 @@ export class GroupManager extends React.Component<{}> {
/**
* Helper method that sets up the group document.
- * @param groupName
- * @param memberEmails
+ * @param groupName
+ * @param memberEmails
*/
createGroupDoc(groupName: string, memberEmails: string[] = []) {
- const name = groupName.toLowerCase() === "admin" ? "Admin" : groupName;
- const groupDoc = new Doc("GROUP:" + name, true);
+ const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName;
+ const groupDoc = new Doc('GROUP:' + name, true);
groupDoc.title = name;
groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
groupDoc.members = JSON.stringify(memberEmails);
@@ -141,12 +150,12 @@ export class GroupManager extends React.Component<{}> {
/**
* Helper method that adds a group document to the database of group documents and @returns whether it was successfully added or not.
- * @param groupDoc
+ * @param groupDoc
*/
addGroup(groupDoc: Doc): boolean {
if (this.GroupManagerDoc) {
- Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc);
- this.GroupManagerDoc["data-lastModified"] = new DateField;
+ Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc);
+ this.GroupManagerDoc['data-lastModified'] = new DateField();
return true;
}
return false;
@@ -154,20 +163,20 @@ export class GroupManager extends React.Component<{}> {
/**
* Deletes a group from the database of group documents and @returns whether the group was deleted or not.
- * @param group
+ * @param group
*/
@action
deleteGroup(group: Doc): boolean {
if (group) {
if (this.GroupManagerDoc && this.hasEditAccess(group)) {
- Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
+ Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group);
SharingManager.Instance.removeGroup(group);
const members = JSON.parse(StrCast(group.members));
if (members.includes(Doc.CurrentUserEmail)) {
const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
}
- this.GroupManagerDoc["data-lastModified"] = new DateField;
+ this.GroupManagerDoc['data-lastModified'] = new DateField();
if (group === this.currentGroup) {
this.currentGroup = undefined;
}
@@ -179,8 +188,8 @@ export class GroupManager extends React.Component<{}> {
/**
* Adds a member to a group.
- * @param groupDoc
- * @param email
+ * @param groupDoc
+ * @param email
*/
addMemberToGroup(groupDoc: Doc, email: string) {
if (this.hasEditAccess(groupDoc)) {
@@ -188,14 +197,14 @@ export class GroupManager extends React.Component<{}> {
!memberList.includes(email) && memberList.push(email);
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.shareWithAddedMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField);
+ this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField());
}
}
/**
* Removes a member from the group.
- * @param groupDoc
- * @param email
+ * @param groupDoc
+ * @param email
*/
removeMemberFromGroup(groupDoc: Doc, email: string) {
if (this.hasEditAccess(groupDoc)) {
@@ -205,27 +214,27 @@ export class GroupManager extends React.Component<{}> {
const user = memberList.splice(index, 1)[0];
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.removeMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField);
+ this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField());
}
}
}
/**
* Handles changes in the users selected in the "Select users" dropdown.
- * @param selectedOptions
+ * @param selectedOptions
*/
@action
handleChange = (selectedOptions: any) => {
this.selectedUsers = selectedOptions as UserOptions[];
- }
+ };
/**
* Creates the group when the enter key has been pressed (when in the input).
- * @param e
+ * @param e
*/
handleKeyDown = (e: React.KeyboardEvent) => {
- e.key === "Enter" && this.createGroup();
- }
+ e.key === 'Enter' && this.createGroup();
+ };
/**
* Handles the input of required fields in the setup of a group and resets the relevant variables.
@@ -234,32 +243,37 @@ export class GroupManager extends React.Component<{}> {
createGroup = () => {
const { value } = this.inputRef.current!;
if (!value) {
- alert("Please enter a group name");
+ alert('Please enter a group name');
return;
}
- if (["admin", "public", "override"].includes(value.toLowerCase())) {
- if (value.toLowerCase() !== "admin" || (value.toLowerCase() === "admin" && this.getGroup("Admin"))) {
+ if (['admin', 'public', 'override'].includes(value.toLowerCase())) {
+ if (value.toLowerCase() !== 'admin' || (value.toLowerCase() === 'admin' && this.getGroup('Admin'))) {
alert(`You cannot override the ${value.charAt(0).toUpperCase() + value.slice(1)} group`);
return;
}
}
if (this.getGroup(value)) {
- alert("Please select a unique group name");
+ alert('Please select a unique group name');
return;
}
- this.createGroupDoc(value, this.selectedUsers?.map(user => user.value));
+ this.createGroupDoc(
+ value,
+ this.selectedUsers?.map(user => user.value)
+ );
this.selectedUsers = null;
- this.inputRef.current!.value = "";
- this.buttonColour = "#979797";
+ this.inputRef.current!.value = '';
+ this.buttonColour = '#979797';
const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect();
TaskCompletionBox.popupX = left - 2 * width;
TaskCompletionBox.popupY = top;
- TaskCompletionBox.textDisplayed = "Group created!";
+ TaskCompletionBox.textDisplayed = 'Group created!';
TaskCompletionBox.taskCompleted = true;
- setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
-
- }
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2000
+ );
+ };
/**
* @returns the MainViewModal which allows the user to create groups.
@@ -268,50 +282,43 @@ export class GroupManager extends React.Component<{}> {
const contents = (
<div className="group-create">
<div className="group-heading" style={{ marginBottom: 0 }}>
- <p><b>New Group</b></p>
- <div className={"close-button"} onClick={action(() => {
- this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false;
- })}>
- <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
+ <p>
+ <b>New Group</b>
+ </p>
+ <div
+ className={'close-button'}
+ onClick={action(() => {
+ this.createGroupModalOpen = false;
+ TaskCompletionBox.taskCompleted = false;
+ })}>
+ <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
</div>
- <input
- className="group-input"
- ref={this.inputRef}
- onKeyDown={this.handleKeyDown}
- autoFocus
- type="text"
- placeholder="Group name"
- onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} />
+ <input className="group-input" ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} />
<Select
isMulti
options={this.options}
onChange={this.handleChange}
- placeholder={"Select users"}
+ placeholder={'Select users'}
value={this.selectedUsers}
closeMenuOnSelect={false}
styles={{
dropdownIndicator: (base, state) => ({
...base,
transition: '0.5s all ease',
- transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
+ transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined,
}),
- multiValue: (base) => ({
+ multiValue: base => ({
...base,
- maxWidth: "50%",
+ maxWidth: '50%',
'&:hover': {
- maxWidth: "unset"
- }
- })
+ maxWidth: 'unset',
+ },
+ }),
}}
/>
- <button
- ref={this.createGroupButtonRef}
- onClick={this.createGroup}
- style={{ background: this.buttonColour }}
- disabled={this.buttonColour === "#979797"}
- >
+ <button ref={this.createGroupButtonRef} onClick={this.createGroup} style={{ background: this.buttonColour }} disabled={this.buttonColour === '#979797'}>
Create
</button>
</div>
@@ -322,8 +329,12 @@ export class GroupManager extends React.Component<{}> {
isDisplayed={this.createGroupModalOpen}
interactive={true}
contents={contents}
- dialogueBoxStyle={{ width: "90%", height: "70%" }}
- closeOnExternalClick={action(() => { this.createGroupModalOpen = false; this.selectedUsers = null; TaskCompletionBox.taskCompleted = false; })}
+ dialogueBoxStyle={{ width: '90%', height: '70%' }}
+ closeOnExternalClick={action(() => {
+ this.createGroupModalOpen = false;
+ this.selectedUsers = null;
+ TaskCompletionBox.taskCompleted = false;
+ })}
/>
);
}
@@ -332,7 +343,6 @@ export class GroupManager extends React.Component<{}> {
* A getter that @returns the main interface for the GroupManager.
*/
private get groupInterface() {
-
const sortGroups = (d1: Doc, d2: Doc) => {
const g1 = StrCast(d1.title);
const g2 = StrCast(d2.title);
@@ -340,62 +350,50 @@ export class GroupManager extends React.Component<{}> {
return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
};
- const groups = this.groupSort === "ascending" ? this.allGroups.sort(sortGroups) : this.groupSort === "descending" ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
+ const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
return (
<div className="group-interface">
{this.groupCreationModal}
- {this.currentGroup ?
- <GroupMemberView
- group={this.currentGroup}
- onCloseButtonClick={action(() => this.currentGroup = undefined)}
- />
- : null}
+ {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null}
<div className="group-heading">
- <p><b>Manage Groups</b></p>
- <button onClick={action(() => this.createGroupModalOpen = true)}>
- <FontAwesomeIcon icon={"plus"} size={"sm"} /> Create Group
+ <p>
+ <b>Manage Groups</b>
+ </p>
+ <button onClick={action(() => (this.createGroupModalOpen = true))}>
+ <FontAwesomeIcon icon={'plus'} size={'sm'} /> Create Group
</button>
- <div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
+ <div className={'close-button'} onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
</div>
<div className="main-container">
- <div
- className="sort-groups"
- onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
- : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
- : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />
- }
+ <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ Name{' '}
+ {this.groupSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.groupSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
</div>
<div className="group-body">
- {groups.map(group =>
- <div
- className="group-row"
- key={StrCast(group.title || group.groupName)}
- >
- <div className="group-name" >{group.title || group.groupName}</div>
- <div className="group-info" onClick={action(() => this.currentGroup = group)}>
- <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+ {groups.map(group => (
+ <div className="group-row" key={StrCast(group.title || group.groupName)}>
+ <div className="group-name">{StrCast(group.title || group.groupName)}</div>
+ <div className="group-info" onClick={action(() => (this.currentGroup = group))}>
+ <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
</div>
</div>
- )}
+ ))}
</div>
</div>
-
</div>
);
}
render() {
- return <MainViewModal
- contents={this.groupInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- dialogueBoxStyle={{ zIndex: 1002 }}
- overlayStyle={{ zIndex: 1001 }}
- closeOnExternalClick={this.close}
- />;
+ return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index e6f75a7db..18aee6444 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,8 +1,8 @@
-import { Doc } from "../../fields/Doc";
-import { DocServer } from "../DocServer";
import * as qs from 'query-string';
-import { Utils, OmitKeys } from "../../Utils";
-import { CurrentUserUtils } from "./CurrentUserUtils";
+import { Doc } from '../../fields/Doc';
+import { OmitKeys, Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
+import { DashboardView } from '../views/DashboardView';
export namespace HistoryUtil {
export interface DocInitializerList {
@@ -10,7 +10,7 @@ export namespace HistoryUtil {
}
export interface DocUrl {
- type: "doc";
+ type: 'doc';
docId: string;
initializers?: {
[docId: string]: DocInitializerList;
@@ -25,11 +25,11 @@ export namespace HistoryUtil {
// const handlers: ((state: ParsedUrl | null) => void)[] = [];
function onHistory(e: PopStateEvent) {
- if (window.location.pathname !== "/home") {
- const url = e.state as ParsedUrl || parseUrl(window.location);
+ if (window.location.pathname !== '/home') {
+ const url = (e.state as ParsedUrl) || parseUrl(window.location);
if (url) {
switch (url.type) {
- case "doc":
+ case 'doc':
onDocUrl(url);
break;
}
@@ -43,13 +43,13 @@ export namespace HistoryUtil {
let _lastStatePush = 0;
export function pushState(state: ParsedUrl) {
if (Date.now() - _lastStatePush > 1000) {
- history.pushState(state, "", createUrl(state));
+ history.pushState(state, '', createUrl(state));
}
_lastStatePush = Date.now();
}
export function replaceState(state: ParsedUrl) {
- history.replaceState(state, "", createUrl(state));
+ history.replaceState(state, '', createUrl(state));
}
function copyState(state: ParsedUrl): ParsedUrl {
@@ -58,8 +58,10 @@ export namespace HistoryUtil {
export function getState(): ParsedUrl {
const state = copyState(history.state);
- state.initializers = state.initializers || {};
- return state;
+ if (state) {
+ state.initializers = state.initializers || {};
+ }
+ return state ?? { initializers: {} };
}
// export function addHandler(handler: (state: ParsedUrl | null) => void) {
@@ -76,10 +78,10 @@ export namespace HistoryUtil {
const parsers: { [type: string]: (pathname: string[], opts: qs.ParsedQuery) => ParsedUrl | undefined } = {};
const stringifiers: { [type: string]: (state: ParsedUrl) => string } = {};
- type ParserValue = true | "none" | "json" | ((value: string) => any);
+ type ParserValue = true | 'none' | 'json' | ((value: string) => any);
type Parser = {
- [key: string]: ParserValue
+ [key: string]: ParserValue;
};
function addParser(type: string, requiredFields: Parser, optionalFields: Parser, customParser?: (pathname: string[], opts: qs.ParsedQuery, current: ParsedUrl) => ParsedUrl | null | undefined) {
@@ -88,9 +90,9 @@ export namespace HistoryUtil {
return value;
}
if (Array.isArray(value)) {
- } else if (parser === true || parser === "json") {
+ } else if (parser === true || parser === 'json') {
value = JSON.parse(value);
- } else if (parser === "none") {
+ } else if (parser === 'none') {
} else {
value = parser(value);
}
@@ -140,29 +142,28 @@ export namespace HistoryUtil {
}
const queryObj = OmitKeys(state, keys).extract;
const query: any = {};
- Object.keys(queryObj).forEach(key => query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]));
+ Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])));
const queryString = qs.stringify(query);
- return path + (queryString ? `?${queryString}` : "");
+ return path + (queryString ? `?${queryString}` : '');
};
}
- addParser("doc", {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
+ addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
if (pathname.length !== 2) return undefined;
current.initializers = current.initializers || {};
const docId = pathname[1];
current.docId = docId;
});
- addStringifier("doc", ["initializers", "readonly", "nro"], (state, current) => {
+ addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => {
return `${current}/${state.docId}`;
});
-
export function parseUrl(location: Location | URL): ParsedUrl | undefined {
const pathname = location.pathname.substring(1);
const search = location.search;
const opts = search.length ? qs.parse(search, { sort: false }) : {};
- const pathnameSplit = pathname.split("/");
+ const pathnameSplit = pathname.split('/');
const type = pathnameSplit[0];
@@ -177,7 +178,7 @@ export namespace HistoryUtil {
if (params.type in stringifiers) {
return stringifiers[params.type](params);
}
- return "";
+ return '';
}
export async function initDoc(id: string, initializer: DocInitializerList) {
@@ -195,7 +196,7 @@ export namespace HistoryUtil {
await Promise.all(Object.keys(init).map(id => initDoc(id, init[id])));
}
if (field instanceof Doc) {
- CurrentUserUtils.openDashboard(Doc.UserDoc(), field, true);
+ DashboardView.openDashboard(field, true);
}
}
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 39e9251a5..37571ae01 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -161,7 +161,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
!this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
- DocumentManager.Instance.jumpToDocument(importContainer, true);
+ DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []);
}
runInAction(() => {
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 3e051dec8..289c5bc51 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -286,6 +286,8 @@ export namespace InteractionUtils {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
+ case TOUCHTYPE:
+ return e.pointerType === TOUCHTYPE;
default: return e.pointerType === type;
}
}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
new file mode 100644
index 000000000..f75ac24f5
--- /dev/null
+++ b/src/client/util/LinkFollower.ts
@@ -0,0 +1,112 @@
+import { action, observable, observe } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
+import { BoolCast, Cast, StrCast } from '../../fields/Types';
+import { LightboxView } from '../views/LightboxView';
+import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView';
+import { DocumentManager } from './DocumentManager';
+import { UndoManager } from './UndoManager';
+
+type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
+/*
+ * link doc:
+ * - anchor1: doc
+ * - anchor2: doc
+ *
+ * group doc:
+ * - type: string representing the group type/name/category
+ * - metadata: doc representing the metadata kvps
+ *
+ * metadata doc:
+ * - user defined kvps
+ */
+export class LinkFollower {
+ // follows a link - if the target is on screen, it highlights/pans to it.
+ // if the target isn't onscreen, then it will open up the target in the lightbox, or in place
+ // depending on the followLinkLocation property of the source (or the link itself as a fallback);
+ public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => {
+ const batch = UndoManager.StartBatch('follow link click');
+ // open up target if it's not already in view ...
+ const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
+ const createTabForTarget = (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ const where = LightboxView.LightboxDoc ? 'lightbox' : StrCast(sourceDoc.followLinkLocation, followLoc);
+ docViewProps.addDocTab(doc, where);
+ setTimeout(() => {
+ const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
+ const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
+ if (targDocView) {
+ targDocView.props.focus(doc, {
+ willZoom: BoolCast(sourceDoc.followLinkZoom, false),
+ afterFocus: (didFocus: boolean) => {
+ finished?.();
+ res(ViewAdjustment.resetView);
+ return new Promise<ViewAdjustment>(res2 => res2(ViewAdjustment.doNothing));
+ },
+ });
+ } else {
+ res(where !== 'inPlace' ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target
+ }
+ });
+ });
+
+ if (!sourceDoc.followLinkZoom) {
+ createTabForTarget(false);
+ } else {
+ // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
+ docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
+ }
+ };
+ LinkFollower.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
+ };
+
+ public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+ const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
+ const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
+ const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
+ const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
+ const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
+ const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
+ const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs;
+ const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1);
+ var count = 0;
+ const allFinished = () => ++count === followLinks.length && finished?.();
+ followLinks.forEach(async linkDoc => {
+ if (linkDoc) {
+ const target = (
+ sourceDoc === linkDoc.anchor1
+ ? linkDoc.anchor2
+ : sourceDoc === linkDoc.anchor2
+ ? linkDoc.anchor1
+ : Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)
+ ? linkDoc.anchor2
+ : linkDoc.anchor1
+ ) as Doc;
+ if (target) {
+ if (target.TourMap) {
+ const fieldKey = Doc.LayoutFieldKey(target);
+ const tour = DocListCast(target[fieldKey]).reverse();
+ LightboxView.SetLightboxDoc(currentContext, undefined, tour);
+ setTimeout(LightboxView.Next);
+ allFinished();
+ } else {
+ const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
+ const containerDoc = containerAnnoDoc || target;
+ var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]);
+ while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
+ containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
+ }
+ const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext;
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'lightbox'), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished);
+ }
+ } else {
+ allFinished();
+ }
+ } else {
+ allFinished();
+ }
+ });
+ }
+}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index df2c02a8d..7a12a8580 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,123 +1,148 @@
-import { action, observable, observe } from "mobx";
-import { computedFn } from "mobx-utils";
-import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { ProxyField } from "../../fields/Proxy";
-import { BoolCast, Cast, StrCast } from "../../fields/Types";
-import { LightboxView } from "../views/LightboxView";
-import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
-import { DocumentManager } from "./DocumentManager";
-import { UndoManager } from "./UndoManager";
-
-type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
-/*
- * link doc:
- * - anchor1: doc
+import { action, observable, observe } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
+import { Cast, StrCast } from '../../fields/Types';
+/*
+ * link doc:
+ * - anchor1: doc
* - anchor2: doc
- *
+ *
* group doc:
* - type: string representing the group type/name/category
* - metadata: doc representing the metadata kvps
- *
+ *
* metadata doc:
- * - user defined kvps
+ * - user defined kvps
*/
export class LinkManager {
-
@observable static _instance: LinkManager;
@observable static userLinkDBs: Doc[] = [];
public static currentLink: Opt<Doc>;
- public static get Instance() { return LinkManager._instance; }
+ public static get Instance() {
+ return LinkManager._instance;
+ }
public static addLinkDB = (linkDb: any) => LinkManager.userLinkDBs.push(linkDb);
+ public static AutoKeywords = 'keywords:Usages';
static links: Doc[] = [];
constructor() {
LinkManager._instance = this;
this.createLinkrelationshipLists();
- setTimeout(() => {
- LinkManager.userLinkDBs = [];
- const addLinkToDoc = (link: Doc) => {
- const a1Prom = link?.anchor1;
- const a2Prom = link?.anchor2;
- Promise.all([a1Prom, a2Prom]).then((all) => {
- const a1 = all[0];
- const a2 = all[1];
- const a1ProtoProm = (link?.anchor1 as Doc)?.proto;
- const a2ProtoProm = (link?.anchor2 as Doc)?.proto;
- Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => {
+ LinkManager.userLinkDBs = [];
+ const addLinkToDoc = (link: Doc) => {
+ const a1Prom = link?.anchor1;
+ const a2Prom = link?.anchor2;
+ Promise.all([a1Prom, a2Prom]).then(all => {
+ const a1 = all[0];
+ const a2 = all[1];
+ const a1ProtoProm = (link?.anchor1 as Doc)?.proto;
+ const a2ProtoProm = (link?.anchor2 as Doc)?.proto;
+ Promise.all([a1ProtoProm, a2ProtoProm]).then(
+ action(all => {
if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
Doc.GetProto(a1)[DirectLinksSym].add(link);
Doc.GetProto(a2)[DirectLinksSym].add(link);
Doc.GetProto(link)[DirectLinksSym].add(link);
}
- }));
- });
- };
- const remLinkFromDoc = (link: Doc) => {
- const a1 = link?.anchor1;
- const a2 = link?.anchor2;
- Promise.all([a1, a2]).then(action(() => {
+ })
+ );
+ });
+ };
+ const remLinkFromDoc = (link: Doc) => {
+ const a1 = link?.anchor1;
+ const a2 = link?.anchor2;
+ Promise.all([a1, a2]).then(
+ action(() => {
if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
Doc.GetProto(a1)[DirectLinksSym].delete(link);
Doc.GetProto(a2)[DirectLinksSym].delete(link);
Doc.GetProto(link)[DirectLinksSym].delete(link);
}
- }));
- };
- const watchUserLinkDB = (userLinkDBDoc: Doc) => {
- LinkManager.links.push(...DocListCast(userLinkDBDoc.data));
- const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
- observe(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes)
+ })
+ );
+ };
+ const watchUserLinkDB = (userLinkDBDoc: Doc) => {
+ LinkManager.links.push(...DocListCast(userLinkDBDoc.data));
+ const toRealField = (field: Field) => (field instanceof ProxyField ? field.value() : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
+ observe(
+ userLinkDBDoc.data as Doc,
+ change => {
+ // observe pushes/splices on a user link DB 'data' field (should only happen for local changes)
switch (change.type as any) {
- case "splice":
+ case 'splice':
(change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
(change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
break;
- case "update": //let oldValue = change.oldValue;
+ case 'update': //let oldValue = change.oldValue;
}
- }, true);
- observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link)
- change => {
- switch (change.type as any) {
- case "update":
- Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => {
- const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length);
- const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length);
+ },
+ true
+ );
+ observe(
+ userLinkDBDoc,
+ 'data', // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link)
+ change => {
+ switch (change.type as any) {
+ case 'update':
+ Promise.all([...((change.oldValue as any as Doc[]) || []), ...((change.newValue as any as Doc[]) || [])]).then(doclist => {
+ const oldDocs = doclist.slice(0, ((change.oldValue as any as Doc[]) || []).length);
+ const newDocs = doclist.slice(((change.oldValue as any as Doc[]) || []).length, doclist.length);
- const added = newDocs?.filter(link => !(oldDocs || []).includes(link));
- const removed = oldDocs?.filter(link => !(newDocs || []).includes(link));
- added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
- removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
- });
- }
- }, true);
- };
- observe(LinkManager.userLinkDBs, change => {
+ const added = newDocs?.filter(link => !(oldDocs || []).includes(link));
+ const removed = oldDocs?.filter(link => !(newDocs || []).includes(link));
+ added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
+ removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
+ });
+ }
+ },
+ true
+ );
+ };
+ observe(
+ LinkManager.userLinkDBs,
+ change => {
switch (change.type as any) {
- case "splice": (change as any).added.forEach(watchUserLinkDB); break;
- case "update": //let oldValue = change.oldValue;
+ case 'splice':
+ (change as any).added.forEach(watchUserLinkDB);
+ break;
+ case 'update': //let oldValue = change.oldValue;
}
- }, true);
- LinkManager.addLinkDB(Doc.LinkDBDoc());
- });
+ },
+ true
+ );
+ LinkManager.addLinkDB(Doc.LinkDBDoc());
+ DocListCastAsync(Doc.LinkDBDoc()?.data).then(dblist =>
+ dblist?.forEach(async link => {
+ // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager
+ const a1 = await Cast(link?.anchor1, Doc, null);
+ const a2 = await Cast(link?.anchor2, Doc, null);
+ })
+ );
}
-
public createLinkrelationshipLists = () => {
//create new lists for link relations and their associated colors if the lists don't already exist
!Doc.UserDoc().linkRelationshipList && (Doc.UserDoc().linkRelationshipList = new List<string>());
!Doc.UserDoc().linkColorList && (Doc.UserDoc().linkColorList = new List<string>());
!Doc.UserDoc().linkRelationshipSizes && (Doc.UserDoc().linkRelationshipSizes = new List<number>());
- }
+ };
public addLink(linkDoc: Doc, checkExists = false) {
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
- Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
+ Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
}
}
- public deleteLink(linkDoc: Doc) { return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc); }
- public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); }
+ public deleteLink(linkDoc: Doc) {
+ return Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
+ }
+ public deleteAllLinksOnAnchor(anchor: Doc) {
+ LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc));
+ }
- public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor
+ public getAllRelatedLinks(anchor: Doc) {
+ return this.relatedLinker(anchor);
+ } // finds all links that contain the given anchor
public getAllDirectLinks(anchor: Doc): Doc[] {
// FIXME:glr Why is Doc undefined?
if (Doc.GetProto(anchor)[DirectLinksSym]) {
@@ -130,18 +155,16 @@ export class LinkManager {
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
const lfield = Doc.LayoutFieldKey(anchor);
if (!anchor || anchor instanceof Promise || Doc.GetProto(anchor) instanceof Promise) {
- console.log("WAITING FOR DOC/PROTO IN LINKMANAGER");
+ console.log('WAITING FOR DOC/PROTO IN LINKMANAGER');
return [];
}
const dirLinks = Doc.GetProto(anchor)[DirectLinksSym];
- const annos = DocListCast(anchor[lfield + "-annotations"]);
- const timelineAnnos = DocListCast(anchor[lfield + "-annotations-timeline"]);
+ const annos = DocListCast(anchor[lfield + '-annotations']);
+ const timelineAnnos = DocListCast(anchor[lfield + '-annotations-timeline']);
if (!annos || !timelineAnnos) {
debugger;
}
- const related = [...annos, ...timelineAnnos].reduce((list, anno) =>
- [...list, ...LinkManager.Instance.relatedLinker(anno)],
- Array.from(dirLinks).slice());
+ const related = [...annos, ...timelineAnnos].reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks).slice());
return related;
}, true);
@@ -149,13 +172,15 @@ export class LinkManager {
public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
const anchorGroups = new Map<string, Array<Doc>>();
this.relatedLinker(anchor).forEach(link => {
- if (!link.linkRelationship || link?.linkRelationship !== "-ungrouped-") {
- const group = anchorGroups.get(StrCast(link.linkRelationship));
- anchorGroups.set(StrCast(link.linkRelationship), group ? [...group, link] : [link]);
+ if (link.linkRelationship && link.linkRelationship !== '-ungrouped-') {
+ const relation = StrCast(link.linkRelationship);
+ const anchorRelation = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation;
+ const group = anchorGroups.get(anchorRelation);
+ anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
} else {
// if link is in no groups then put it in default group
- const group = anchorGroups.get("*");
- anchorGroups.set("*", group ? [...group, link] : [link]);
+ const group = anchorGroups.get('*');
+ anchorGroups.set('*', group ? [...group, link] : [link]);
}
});
return anchorGroups;
@@ -173,81 +198,4 @@ export class LinkManager {
if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
-
-
- // follows a link - if the target is on screen, it highlights/pans to it.
- // if the target isn't onscreen, then it will open up the target in the lightbox, or in place
- // depending on the followLinkLocation property of the source (or the link itself as a fallback);
- public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => {
- const batch = UndoManager.StartBatch("follow link click");
- // open up target if it's not already in view ...
- const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
- const createTabForTarget = (didFocus: boolean) => new Promise<ViewAdjustment>(res => {
- const where = LightboxView.LightboxDoc ? "lightbox" : StrCast(sourceDoc.followLinkLocation, followLoc);
- docViewProps.addDocTab(doc, where);
- setTimeout(() => {
- const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
- const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
- if (targDocView) {
- targDocView.props.focus(doc, {
- willZoom: BoolCast(sourceDoc.followLinkZoom, false),
- afterFocus: (didFocus: boolean) => {
- finished?.();
- res(ViewAdjustment.resetView);
- return new Promise<ViewAdjustment>(res2 => res2(ViewAdjustment.doNothing));
- }
- });
- } else {
- res(where !== "inPlace" ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target
- }
- });
- });
-
- if (!sourceDoc.followLinkZoom) {
- createTabForTarget(false);
- } else {
- // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
- docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
- }
- };
- LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
- }
-
- public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
- const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
- const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
- const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
- const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
- const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
- const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
- const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
- const followLinks = linkDocList.length ? (sourceDoc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
- followLinks.forEach(async linkDoc => {
- if (linkDoc) {
- const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 :
- (Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
- if (target) {
- if (target.TourMap) {
- const fieldKey = Doc.LayoutFieldKey(target);
- const tour = DocListCast(target[fieldKey]).reverse();
- LightboxView.SetLightboxDoc(currentContext, undefined, tour);
- setTimeout(LightboxView.Next);
- finished?.();
- } else {
- const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
- const containerDoc = containerAnnoDoc || target;
- const containerDocContext = Cast(containerDoc?.context, Doc, null);
- const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext;
- const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished);
- }
- } else {
- finished?.();
- }
- } else {
- finished?.();
- }
- });
- }
-
-} \ No newline at end of file
+}
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
new file mode 100644
index 000000000..86bc4c5de
--- /dev/null
+++ b/src/client/util/ReplayMovements.ts
@@ -0,0 +1,208 @@
+import { CollectionFreeFormView } from "../views/collections/collectionFreeForm";
+import { IReactionDisposer, observable, observe, reaction } from "mobx";
+import { Doc } from "../../fields/Doc";
+import { VideoBox } from "../views/nodes/VideoBox";
+import { DocumentManager } from "./DocumentManager";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DocServer } from "../DocServer";
+import { Movement, Presentation } from "./TrackMovements";
+
+export class ReplayMovements {
+ private timers: NodeJS.Timeout[] | null;
+ private videoBoxDisposeFunc: IReactionDisposer | null;
+ private videoBox: VideoBox | null;
+ private isPlaying: boolean;
+
+
+ // create static instance and getter for global use
+ @observable static _instance: ReplayMovements;
+ static get Instance(): ReplayMovements { return ReplayMovements._instance }
+ constructor() {
+ // init the global instance
+ ReplayMovements._instance = this;
+
+ // instance vars for replaying
+ this.timers = null;
+ this.videoBoxDisposeFunc = null;
+ this.videoBox = null;
+ this.isPlaying = false;
+ }
+
+ // pausing movements will dispose all timers that are planned to replay the movements
+ // play movemvents will recreate them when the user resumes the presentation
+ pauseMovements = (): undefined | Error => {
+ if (!this.isPlaying) {
+ // console.warn('[recordingApi.ts] pauseMovements(): already on paused');
+ return;
+ }
+ Doc.UserDoc().presentationMode = 'none';
+
+ this.isPlaying = false
+ // TODO: set userdoc presentMode to browsing
+ this.timers?.map(timer => clearTimeout(timer))
+ }
+
+ setVideoBox = async (videoBox: VideoBox) => {
+ // console.info('setVideoBox', videoBox);
+ if (this.videoBox !== null) { console.warn('setVideoBox on already videoBox'); }
+ if (this.videoBoxDisposeFunc !== null) { console.warn('setVideoBox on already videoBox dispose func'); this.videoBoxDisposeFunc(); }
+
+
+ const { presentation } = videoBox;
+ if (presentation == null) { console.warn('setVideoBox on null videoBox presentation'); return; }
+
+ let docIdtoDoc: Map<string, Doc> = new Map();
+ try {
+ docIdtoDoc = await this.loadPresentation(presentation);
+ } catch {
+ console.error('[recordingApi.ts] setVideoBox(): error loading presentation - no replay movements');
+ throw 'error loading docs from server';
+ }
+
+
+ this.videoBoxDisposeFunc =
+ reaction(() => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }),
+ ({ playing, timeViewed }) =>
+ playing ? this.playMovements(presentation, docIdtoDoc, timeViewed) : this.pauseMovements()
+ );
+ this.videoBox = videoBox;
+ }
+
+ removeVideoBox = () => {
+ if (this.videoBoxDisposeFunc == null) { console.warn('removeVideoBox on null videoBox'); return; }
+ this.videoBoxDisposeFunc();
+
+ this.videoBox = null;
+ this.videoBoxDisposeFunc = null;
+ }
+
+ // should be called from interacting with the screen
+ pauseFromInteraction = () => {
+ this.videoBox?.Pause();
+
+ this.pauseMovements();
+ }
+
+ loadPresentation = async (presentation: Presentation) => {
+ const { movements } = presentation;
+ if (movements === null) {
+ throw '[recordingApi.ts] followMovements() failed: no presentation data';
+ }
+
+ // generate a set of all unique docIds
+ const docIds = new Set<string>();
+ for (const {docId} of movements) {
+ if (!docIds.has(docId)) docIds.add(docId);
+ }
+
+ const docIdtoDoc = new Map<string, Doc>();
+
+ let refFields = await DocServer.GetRefFields([...docIds.keys()]);
+ for (const docId in refFields) {
+ if (!refFields[docId]) {
+ throw `one field was undefined`;
+ }
+ docIdtoDoc.set(docId, refFields[docId] as Doc);
+ }
+ // console.info('loadPresentation refFields', refFields, docIdtoDoc);
+
+ return docIdtoDoc;
+ }
+
+ // returns undefined if the docView isn't open on the screen
+ getCollectionFFView = (docId: string) => {
+ const isInView = DocumentManager.Instance.getDocumentViewById(docId);
+ if (isInView) { return isInView.ComponentView as CollectionFreeFormView; }
+ }
+
+ // will open the doc in a tab then return the CollectionFFView that holds it
+ openTab = (docId: string, docIdtoDoc: Map<string, Doc>) => {
+ const doc = docIdtoDoc.get(docId);
+ if (doc == undefined) {
+ console.error(`docIdtoDoc did not contain docId ${docId}`)
+ return undefined;
+ }
+ // console.log('openTab', docId, doc);
+ CollectionDockingView.AddSplit(doc, 'right');
+ const docView = DocumentManager.Instance.getDocumentView(doc);
+ // BUG - this returns undefined if the doc is already open
+ return docView?.ComponentView as CollectionFreeFormView;
+ }
+
+ // helper to replay a movement
+ zoomAndPan = (movement: Movement, document: CollectionFreeFormView) => {
+ const { panX, panY, scale } = movement;
+ scale !== 0 && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
+ document.Document._panX = panX;
+ document.Document._panY = panY;
+ }
+
+ getFirstMovements = (movements: Movement[]): Map<string, Movement> => {
+ if (movements === null) return new Map();
+ // generate a set of all unique docIds
+ const docIdtoFirstMove = new Map();
+ for (const move of movements) {
+ const { docId } = move;
+ if (!docIdtoFirstMove.has(docId)) docIdtoFirstMove.set(docId, move);
+ }
+ return docIdtoFirstMove;
+ }
+
+ endPlayingPresentation = () => {
+ this.isPlaying = false;
+ Doc.UserDoc().presentationMode = 'none';
+ }
+
+ public playMovements = (presentation: Presentation, docIdtoDoc: Map<string, Doc>, timeViewed: number = 0) => {
+ // console.info('playMovements', presentation, timeViewed, docIdtoDoc);
+
+ if (presentation.movements === null || presentation.movements.length === 0) { //|| this.playFFView === null) {
+ return new Error('[recordingApi.ts] followMovements() failed: no presentation data')
+ }
+ if (this.isPlaying) return;
+
+ this.isPlaying = true;
+ Doc.UserDoc().presentationMode = 'watching';
+
+ // only get the movements that are remaining in the video time left
+ const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000)
+
+ const handleFirstMovements = () => {
+ // if the first movement is a closed tab, open it
+ const firstMovement = filteredMovements[0];
+ const isClosed = this.getCollectionFFView(firstMovement.docId) === undefined;
+ if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc);
+
+ // for the open tabs, set it to the first move
+ const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
+ for (const [docId, firstMove] of docIdtoFirstMove) {
+ const colFFView = this.getCollectionFFView(docId);
+ if (colFFView) this.zoomAndPan(firstMove, colFFView);
+ }
+ }
+ handleFirstMovements();
+
+
+ // make timers that will execute each movement at the correct replay time
+ this.timers = filteredMovements.map(movement => {
+ const timeDiff = movement.time - timeViewed * 1000
+
+ return setTimeout(() => {
+ const collectionFFView = this.getCollectionFFView(movement.docId);
+ if (collectionFFView) {
+ this.zoomAndPan(movement, collectionFFView);
+ } else {
+ // tab wasn't open - open it and play the movement
+ const openedColFFView = this.openTab(movement.docId, docIdtoDoc);
+ console.log('openedColFFView', openedColFFView);
+ openedColFFView && this.zoomAndPan(movement, openedColFFView);
+ }
+
+ // if last movement, presentation is done -> cleanup :)
+ if (movement === filteredMovements[filteredMovements.length - 1]) {
+ this.endPlayingPresentation();
+ }
+ }, timeDiff);
+ });
+ }
+}
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 3b0a47b54..ea2bf6551 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -5,12 +5,11 @@
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
// @ts-ignore
import * as typescriptlib from '!!raw-loader!./type_decls.d';
-import * as ts from "typescript";
-import { Doc, Field } from "../../fields/Doc";
-import { scriptingGlobals, ScriptingGlobals } from "./ScriptingGlobals";
+import * as ts from 'typescript';
+import { Doc, Field } from '../../fields/Doc';
+import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals';
export { ts };
-
export interface ScriptSuccess {
success: true;
result: any;
@@ -46,7 +45,6 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is
return false;
}
-
function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error);
if ((options.typecheck !== false && errors.length) || !script) {
@@ -63,7 +61,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
const argsArray: any[] = [];
for (const name of customParams) {
- if (name === "this") {
+ if (name === 'this') {
continue;
}
if (name in args) {
@@ -86,11 +84,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
return { success: true, result };
} catch (error) {
-
if (batch) {
batch.end();
}
- onError?.(script + " " + error);
+ onError?.(script + ' ' + error);
return { success: false, error, result: errorVal };
}
};
@@ -151,16 +148,16 @@ class ScriptingCompilerHost {
}
export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
-export type TraverserParam = Traverser | { onEnter: Traverser, onLeave: Traverser };
+export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser };
export type Transformer = {
- transformer: ts.TransformerFactory<ts.SourceFile>,
- getVars?: () => { capturedVariables: { [name: string]: Field } }
+ transformer: ts.TransformerFactory<ts.SourceFile>;
+ getVars?: () => { capturedVariables: { [name: string]: Field } };
};
export interface ScriptOptions {
requiredType?: string; // does function required a typed return value
- addReturn?: boolean; // does the compiler automatically add a return statement
+ addReturn?: boolean; // does the compiler automatically add a return statement
params?: { [name: string]: string }; // list of function parameters and their types
- capturedVariables?: { [name: string]: Field }; // list of captured variables
+ capturedVariables?: { [name: string]: Doc | number | string | boolean }; // list of captured variables
typecheck?: boolean; // should the compiler perform typechecking
editable?: boolean; // can the script edit Docs
traverser?: TraverserParam;
@@ -169,24 +166,28 @@ export interface ScriptOptions {
}
// function forEachNode(node:ts.Node, fn:(node:any) => void);
-function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = "") {
- return onEnter(node, indentation) || ts.forEachChild(node, (n: any) => {
- forEachNode(n, onEnter, onExit, indentation + " ");
- }) || (onExit && onExit(node, indentation));
+function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = '') {
+ return (
+ onEnter(node, indentation) ||
+ ts.forEachChild(node, (n: any) => {
+ forEachNode(n, onEnter, onExit, indentation + ' ');
+ }) ||
+ (onExit && onExit(node, indentation))
+ );
}
export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
- const { requiredType = "", addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
+ const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
if (options.params && !options.params.this) options.params.this = Doc.name;
if (options.params && !options.params.self) options.params.self = Doc.name;
if (options.globals) {
ScriptingGlobals.setScriptingGlobals(options.globals);
}
- const host = new ScriptingCompilerHost;
+ const host = new ScriptingCompilerHost();
if (options.traverser) {
const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
- const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
- const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
+ const onEnter = typeof options.traverser === 'object' ? options.traverser.onEnter : options.traverser;
+ const onLeave = typeof options.traverser === 'object' ? options.traverser.onLeave : undefined;
forEachNode(sourceFile, onEnter, onLeave);
}
if (options.transformer) {
@@ -199,17 +200,17 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
}
const transformed = result.transformed;
const printer = ts.createPrinter({
- newLine: ts.NewLineKind.LineFeed
+ newLine: ts.NewLineKind.LineFeed,
});
script = printer.printFile(transformed[0]);
result.dispose();
}
const paramNames: string[] = [];
- if ("this" in params || "this" in capturedVariables) {
- paramNames.push("this");
+ if ('this' in params || 'this' in capturedVariables) {
+ paramNames.push('this');
}
for (const key in params) {
- if (key === "this") continue;
+ if (key === 'this') continue;
paramNames.push(key);
}
const paramList = paramNames.map(key => {
@@ -217,21 +218,21 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
return `${key}: ${val}`;
});
for (const key in capturedVariables) {
- if (key === "this") continue;
+ if (key === 'this') continue;
const val = capturedVariables[key];
paramNames.push(key);
- paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
}
- const paramString = paramList.join(", ");
- const body = addReturn ? `return ${script};` : `return ${script};`;
+ const paramString = paramList.join(', ');
+ const body = addReturn && !script.startsWith('{ return') ? `return ${script};` : script;
const reqTypes = requiredType ? `: ${requiredType}` : '';
const funcScript = `(function(${paramString})${reqTypes} { ${body} })`;
- host.writeFile("file.ts", funcScript);
+ host.writeFile('file.ts', funcScript);
if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
- const program = ts.createProgram(["file.ts"], {}, host);
+ const program = ts.createProgram(['file.ts'], {}, host);
const testResult = program.emit();
- const outputText = host.readFile("file.js");
+ const outputText = host.readFile('file.js');
const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index a209874a3..d4620ae3f 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,21 +1,24 @@
-import React = require("react");
+import React = require('react');
-export class ScrollBox extends React.Component {
+export class ScrollBox extends React.Component<React.PropsWithChildren<{}>> {
onWheel = (e: React.WheelEvent) => {
- if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom
+ if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+ // If the element has a scroll bar, then we don't want the containing collection to zoom
e.stopPropagation();
}
- }
+ };
render() {
return (
- <div style={{
- overflow: "auto",
- width: "100%",
- height: "100%",
- }} onWheel={this.onWheel}>
+ <div
+ style={{
+ overflow: 'auto',
+ width: '100%',
+ height: '100%',
+ }}
+ onWheel={this.onWheel}>
{this.props.children}
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index b71086561..1c84af94a 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,15 +1,13 @@
-import { action, observable, ObservableMap } from "mobx";
-import { computedFn } from "mobx-utils";
-import { Doc, Opt } from "../../fields/Doc";
-import { DocumentType } from "../documents/DocumentTypes";
-import { CollectionViewType } from "../views/collections/CollectionView";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { CurrentUserUtils } from "./CurrentUserUtils";
+import { action, observable, ObservableMap } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { Doc, Opt } from '../../fields/Doc';
+import { DocCast } from '../../fields/Types';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { ScriptingGlobals } from './ScriptingGlobals';
export namespace SelectionManager {
-
class Manager {
-
@observable IsDragging: boolean = false;
SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
@observable SelectedSchemaDocument: Doc | undefined;
@@ -63,16 +61,21 @@ export namespace SelectionManager {
manager.SelectSchemaViewDoc(document);
}
- const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
+ const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {
+ // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
return manager.SelectedViews.get(doc) ? true : false;
});
// computed functions, such as used in IsSelected generate errors if they're called outside of a
// reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature
// to avoid unnecessary mobx invalidations when running inside a reaction.
export function IsSelected(doc: DocumentView | undefined, outsideReaction?: boolean): boolean {
- return !doc ? false : outsideReaction ?
- manager.SelectedViews.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
- IsSelectedCache(doc);
+ return !doc
+ ? false
+ : outsideReaction
+ ? manager.SelectedViews.get(doc)
+ ? true
+ : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
+ : IsSelectedCache(doc);
}
export function DeselectAll(except?: Doc): void {
@@ -96,4 +99,8 @@ export namespace SelectionManager {
export function Docs(): Doc[] {
return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
}
-} \ No newline at end of file
+}
+ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
+ let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
+ return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
+});
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 6a26dfdc7..cf143c5e8 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,27 +1,29 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { ColorState, SketchPicker } from "react-color";
-import { Doc } from "../../fields/Doc";
-import { BoolCast, StrCast, Cast } from "../../fields/Types";
-import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils";
-import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
-import { DocServer } from "../DocServer";
-import { Networking } from "../Network";
-import { MainViewModal } from "../views/MainViewModal";
-import { CurrentUserUtils } from "./CurrentUserUtils";
-import { GroupManager } from "./GroupManager";
-import "./SettingsManager.scss";
-import { undoBatch } from "./UndoManager";
-const higflyout = require("@hig/flyout");
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { ColorState, SketchPicker } from 'react-color';
+import { Doc } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { BoolCast, Cast, StrCast } from '../../fields/Types';
+import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
+import { DocServer } from '../DocServer';
+import { Networking } from '../Network';
+import { MainViewModal } from '../views/MainViewModal';
+import { FontIconBox } from '../views/nodes/button/FontIconBox';
+import { DragManager } from './DragManager';
+import { GroupManager } from './GroupManager';
+import './SettingsManager.scss';
+import { undoBatch } from './UndoManager';
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
export enum ColorScheme {
- Dark = "-Dark",
- Light = "-Light",
- System = "-MatchSystem"
+ Dark = '-Dark',
+ Light = '-Light',
+ System = '-MatchSystem',
}
@observer
@@ -29,295 +31,363 @@ export class SettingsManager extends React.Component<{}> {
public static Instance: SettingsManager;
static _settingsStyle = addStyleSheet();
@observable private isOpen = false;
- @observable private passwordResultText = "";
+ @observable private passwordResultText = '';
@observable private playgroundMode = false;
- @observable private curr_password = "";
- @observable private new_password = "";
- @observable private new_confirm = "";
- @observable activeTab = "Accounts";
+ @observable private curr_password = '';
+ @observable private new_password = '';
+ @observable private new_confirm = '';
+ @observable activeTab = 'Accounts';
- @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
- @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; }
+ @observable public static propertiesWidth: number = 0;
+ @observable public static headerBarHeight: number = 0;
+
+ @computed get backgroundColor() {
+ return Doc.UserDoc().activeCollectionBackground;
+ }
+ @computed get colorScheme() {
+ return Doc.ActiveDashboard?.colorScheme;
+ }
constructor(props: {}) {
super(props);
SettingsManager.Instance = this;
}
- public close = action(() => this.isOpen = false);
- public open = action(() => this.isOpen = true);
+ public close = action(() => (this.isOpen = false));
+ public open = action(() => (this.isOpen = true));
private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
private changePassword = async () => {
if (!(this.curr_password && this.new_password && this.new_confirm)) {
- runInAction(() => this.passwordResultText = "Error: Hey, we're missing some fields!");
+ runInAction(() => (this.passwordResultText = "Error: Hey, we're missing some fields!"));
} else {
const passwordBundle = { curr_pass: this.curr_password, new_pass: this.new_password, new_confirm: this.new_confirm };
const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
- runInAction(() => this.passwordResultText = error ? "Error: " + error[0].msg + "..." : "Password successfully updated!");
+ runInAction(() => (this.passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!'));
}
- }
-
- @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice");
- @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value ? "title" : undefined);
- @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value);
- @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value);
- @undoBatch switchActiveBackgroundColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex));
- @undoBatch switchUserColor = action((color: ColorState) => { Doc.SharingDoc().userColor = undefined; Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex); });
- @undoBatch
- playgroundModeToggle = action(() => {
+ };
+
+ @undoBatch selectUserMode = action((e: React.ChangeEvent) => (Doc.noviceMode = (e.currentTarget as any)?.value === 'Novice'));
+ @undoBatch changeShowTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().showTitle = (e.currentTarget as any).value ? 'title' : undefined));
+ @undoBatch changeFontFamily = action((e: React.ChangeEvent) => (Doc.UserDoc().fontFamily = (e.currentTarget as any).value));
+ @undoBatch changeFontSize = action((e: React.ChangeEvent) => (Doc.UserDoc().fontSize = (e.currentTarget as any).value));
+ @undoBatch switchActiveBackgroundColor = action((color: ColorState) => (Doc.UserDoc().activeCollectionBackground = String(color.hex)));
+ @undoBatch switchUserColor = action((color: ColorState) => {
+ Doc.SharingDoc().userColor = undefined;
+ Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex);
+ });
+ @undoBatch playgroundModeToggle = action(() => {
this.playgroundMode = !this.playgroundMode;
if (this.playgroundMode) {
DocServer.Control.makeReadOnly();
- addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "pink !important" });
- }
- else DocServer.Control.makeEditable();
+ addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
+ } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
});
@undoBatch
@action
changeColorScheme = action((e: React.ChangeEvent) => {
+ const activeDashboard = Doc.ActiveDashboard;
+ if (!activeDashboard) return;
const scheme: ColorScheme = (e.currentTarget as any).value;
switch (scheme) {
case ColorScheme.Light:
- CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
- addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
+ activeDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: '#d3d3d3 !important' });
break;
case ColorScheme.Dark:
- CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark;
- addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" });
+ activeDashboard.colorScheme = ColorScheme.Dark;
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: 'black !important' });
break;
- case ColorScheme.System: default:
+ case ColorScheme.System:
+ default:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
- CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+ activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
});
break;
}
});
-
@computed get colorsContent() {
- const colorBox = (func: (color: ColorState) => void) => <SketchPicker onChange={func} color={StrCast(this.backgroundColor)}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
- '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
- '#FFFFFF', '#f1efeb', 'transparent']} />;
-
- const colorFlyout = <div className="colorFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchActiveBackgroundColor)}>
- <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()} >
- <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
- </div>
- </Flyout>
- </div>;
- const userColorFlyout = <div className="colorFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchUserColor)}>
- <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()} >
- <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
- </div>
- </Flyout>
- </div>;
+ const colorBox = (func: (color: ColorState) => void) => (
+ <SketchPicker
+ onChange={func}
+ color={StrCast(this.backgroundColor)}
+ presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ />
+ );
+
+ const colorFlyout = (
+ <div className="colorFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchActiveBackgroundColor)}>
+ <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
+ </div>
+ </Flyout>
+ </div>
+ );
+ const userColorFlyout = (
+ <div className="colorFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchUserColor)}>
+ <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
+ </div>
+ </Flyout>
+ </div>
+ );
const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
- const schemeMap = ["Light", "Dark", "Match system"];
+ const schemeMap = ['Light', 'Dark', 'Match system'];
- return <div className="colors-content">
- <div className="preferences-color">
- <div className="preferences-color-text">Background Color</div>
- {colorFlyout}
- </div>
- <div className="preferences-color">
- <div className="preferences-color-text">Border/Header Color</div>
- {userColorFlyout}
- </div>
- <div className="preferences-colorScheme">
- <div className="preferences-color-text">Color Scheme</div>
- <div className="preferences-color-controls">
- <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme)}>
- {colorSchemes.map((scheme, i) => <option key={scheme} value={scheme}> {schemeMap[i]} </option>)}
- </select>
+ return (
+ <div className="colors-content">
+ <div className="preferences-color">
+ <div className="preferences-color-text">Background Color</div>
+ {colorFlyout}
+ </div>
+ <div className="preferences-color">
+ <div className="preferences-color-text">Border/Header Color</div>
+ {userColorFlyout}
+ </div>
+ <div className="preferences-colorScheme">
+ <div className="preferences-color-text">Color Scheme</div>
+ <div className="preferences-color-controls">
+ <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(Doc.ActiveDashboard?.colorScheme)}>
+ {colorSchemes.map((scheme, i) => (
+ <option key={scheme} value={scheme}>
+ {' '}
+ {schemeMap[i]}{' '}
+ </option>
+ ))}
+ </select>
+ </div>
</div>
</div>
- </div>;
+ );
}
@computed get formatsContent() {
- return <div className="prefs-content">
- <div>
- <input type="checkbox" onChange={e => Doc.UserDoc().showTitle = Doc.UserDoc().showTitle ? undefined : "creationDate"} checked={Doc.UserDoc().showTitle !== undefined} />
- <div className="preferences-check">Show doc header</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => Doc.UserDoc()["documentLinksButton-fullMenu"] = !Doc.UserDoc()["documentLinksButton-fullMenu"]}
- checked={BoolCast(Doc.UserDoc()["documentLinksButton-fullMenu"])} />
- <div className="preferences-check">Show full toolbar</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => Doc.UserDoc()._raiseWhenDragged = !Doc.UserDoc()._raiseWhenDragged}
- checked={BoolCast(Doc.UserDoc()._raiseWhenDragged)} />
- <div className="preferences-check">Raise on drag</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => Doc.UserDoc()._showLabel = !Doc.UserDoc()._showLabel}
- checked={BoolCast(Doc.UserDoc()._showLabel)} />
- <div className="preferences-check">Show tool button labels</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => Doc.UserDoc()._showMenuLabel = !Doc.UserDoc()._showMenuLabel}
- checked={BoolCast(Doc.UserDoc()._showMenuLabel)} />
- <div className="preferences-check">Show menu button labels</div>
+ return (
+ <div className="prefs-content">
+ <div>
+ <input type="checkbox" onChange={e => (Doc.UserDoc().showTitle = Doc.UserDoc().showTitle ? undefined : 'creationDate')} checked={Doc.UserDoc().showTitle !== undefined} />
+ <div className="preferences-check">Show doc header</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} checked={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} />
+ <div className="preferences-check">Show full toolbar</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())} checked={DragManager.GetRaiseWhenDragged()} />
+ <div className="preferences-check">Raise on drag</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} />
+ <div className="preferences-check">Show button labels</div>
+ </div>
</div>
- </div>;
+ );
}
@computed get appearanceContent() {
-
- return <div className="tab-content appearances-content">
- <div className="tab-column">
- <div className="tab-column-title">Colors</div>
- <div className="tab-column-content">{this.colorsContent}</div>
- </div>
- <div className="tab-column">
- <div className="tab-column-title">Formats</div>
- <div className="tab-column-content">{this.formatsContent}</div>
+ return (
+ <div className="tab-content appearances-content">
+ <div className="tab-column">
+ <div className="tab-column-title">Colors</div>
+ <div className="tab-column-content">{this.colorsContent}</div>
+ </div>
+ <div className="tab-column">
+ <div className="tab-column-title">Formats</div>
+ <div className="tab-column-content">{this.formatsContent}</div>
+ </div>
</div>
- </div>;
+ );
}
@computed get textContent() {
-
- const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text", "Roboto"];
- const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"];
+ const fontFamilies = ['Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text', 'Roboto'];
+ const fontSizes = ['7px', '8px', '9px', '10px', '12px', '14px', '16px', '18px', '20px', '24px', '32px', '48px', '72px'];
return (
<div className="tab-content appearances-content">
<div className="preferences-font">
<div className="preferences-font-text">Default Font</div>
<div className="preferences-font-controls">
- <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7px")}>
- {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
+ <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, '7px')}>
+ {fontSizes.map(size => (
+ <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}>
+ {' '}
+ {size}{' '}
+ </option>
+ ))}
</select>
- <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
- {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
+ <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, 'Times New Roman')}>
+ {fontFamilies.map(font => (
+ <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}>
+ {' '}
+ {font}{' '}
+ </option>
+ ))}
</select>
</div>
</div>
- </div>);
+ </div>
+ );
}
@action
changeVal = (e: React.ChangeEvent, pass: string) => {
const value = (e.target as any).value;
switch (pass) {
- case "curr": this.curr_password = value; break;
- case "new": this.new_password = value; break;
- case "conf": this.new_confirm = value; break;
+ case 'curr':
+ this.curr_password = value;
+ break;
+ case 'new':
+ this.new_password = value;
+ break;
+ case 'conf':
+ this.new_confirm = value;
+ break;
}
- }
+ };
@computed get passwordContent() {
- return <div className="password-content">
- <div className="password-content-inputs">
- <input className="password-inputs" type="password" placeholder="current password" onChange={e => this.changeVal(e, "curr")} />
- <input className="password-inputs" type="password" placeholder="new password" onChange={e => this.changeVal(e, "new")} />
- <input className="password-inputs" type="password" placeholder="confirm new password" onChange={e => this.changeVal(e, "conf")} />
- </div>
- <div className="password-content-buttons">
- {!this.passwordResultText ? (null) : <div className={`${this.passwordResultText.startsWith("Error") ? "error" : "success"}-text`}>{this.passwordResultText}</div>}
- <a className="password-forgot" href="/forgotPassword">forgot password?</a>
- <button className="password-submit" onClick={this.changePassword}>submit</button>
+ return (
+ <div className="password-content">
+ <div className="password-content-inputs">
+ <input className="password-inputs" type="password" placeholder="current password" onChange={e => this.changeVal(e, 'curr')} />
+ <input className="password-inputs" type="password" placeholder="new password" onChange={e => this.changeVal(e, 'new')} />
+ <input className="password-inputs" type="password" placeholder="confirm new password" onChange={e => this.changeVal(e, 'conf')} />
+ </div>
+ <div className="password-content-buttons">
+ {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
+ <a className="password-forgot" href="/forgotPassword">
+ forgot password?
+ </a>
+ <button className="password-submit" onClick={this.changePassword}>
+ submit
+ </button>
+ </div>
</div>
- </div>;
+ );
}
@computed get accountOthersContent() {
- return <div className="account-others-content">
- <button onClick={this.googleAuthorize} value="data">Authorize Google Acc</button>
- </div>;
+ return (
+ <div className="account-others-content">
+ <button onClick={this.googleAuthorize} value="data">
+ Authorize Google Acc
+ </button>
+ </div>
+ );
}
@computed get accountsContent() {
- return <div className="tab-content accounts-content">
- <div className="tab-column">
- <div className="tab-column-title">Password</div>
- <div className="tab-column-content">{this.passwordContent}</div>
- </div>
- <div className="tab-column">
- <div className="tab-column-title">Others</div>
- <div className="tab-column-content">{this.accountOthersContent}</div>
+ return (
+ <div className="tab-content accounts-content">
+ <div className="tab-column">
+ <div className="tab-column-title">Password</div>
+ <div className="tab-column-content">{this.passwordContent}</div>
+ </div>
+ <div className="tab-column">
+ <div className="tab-column-title">Others</div>
+ <div className="tab-column-content">{this.accountOthersContent}</div>
+ </div>
</div>
- </div>;
+ );
}
@computed get modesContent() {
- return <div className="tab-content modes-content">
- <div className="tab-column">
- <div className="tab-column-title">Modes</div>
- <div className="tab-column-content">
- <select className="modes-select" onChange={this.selectUserMode} defaultValue={Doc.UserDoc().noviceMode ? "Novice" : "Developer"}>
- <option key={"Novice"} value={"Novice"}> Novice </option>
- <option key={"Developer"} value={"Developer"}> Developer</option>
- </select>
- <div className="modes-playground">
- <input className="playground-check" type="checkbox" checked={this.playgroundMode} onChange={this.playgroundModeToggle} />
- <div className="playground-text">Playground Mode</div>
+ return (
+ <div className="tab-content modes-content">
+ <div className="tab-column">
+ <div className="tab-column-title">Modes</div>
+ <div className="tab-column-content">
+ <select className="modes-select" onChange={this.selectUserMode} defaultValue={Doc.noviceMode ? 'Novice' : 'Developer'}>
+ <option key={'Novice'} value={'Novice'}>
+ {' '}
+ Novice{' '}
+ </option>
+ <option key={'Developer'} value={'Developer'}>
+ {' '}
+ Developer
+ </option>
+ </select>
+ <div className="modes-playground">
+ <input className="playground-check" type="checkbox" checked={this.playgroundMode} onChange={this.playgroundModeToggle} />
+ <div className="playground-text">Playground Mode</div>
+ </div>
</div>
</div>
- </div>
- <div className="tab-column">
- <div className="tab-column-title">Permissions</div>
- <div className="tab-column-content">
- <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
- <div className="default-acl">
- <input className="acl-check" type="checkbox" checked={BoolCast(Doc.UserDoc()?.defaultAclPrivate)}
- onChange={action(() => Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} />
- <div className="acl-text">Default access private</div>
+ <div className="tab-column">
+ <div className="tab-column-title">Permissions</div>
+ <div className="tab-column-content">
+ <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
+ <div className="default-acl">
+ <input className="acl-check" type="checkbox" checked={BoolCast(Doc.defaultAclPrivate)} onChange={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} />
+ <div className="acl-text">Default access private</div>
+ </div>
</div>
</div>
</div>
-
- </div>;
+ );
}
-
private get settingsInterface() {
// const pairs = [{ title: "Password", ele: this.passwordContent }, { title: "Modes", ele: this.modesContent },
// { title: "Accounts", ele: this.accountsContent }, { title: "Preferences", ele: this.preferencesContent }];
- const tabs = [{ title: "Accounts", ele: this.accountsContent }, { title: "Modes", ele: this.modesContent },
- { title: "Appearance", ele: this.appearanceContent }, { title: "Text", ele: this.textContent }];
+ const tabs = [
+ { title: 'Accounts', ele: this.accountsContent },
+ { title: 'Modes', ele: this.modesContent },
+ { title: 'Appearance', ele: this.appearanceContent },
+ { title: 'Text', ele: this.textContent },
+ ];
- return <div className="settings-interface">
- <div className="settings-panel">
- <div className="settings-tabs">
- {tabs.map(tab => <div key={tab.title} className={"tab-control " + (this.activeTab === tab.title ? "active" : "inactive")} onClick={action(() => this.activeTab = tab.title)}>{tab.title}</div>)}
- </div>
+ return (
+ <div className="settings-interface">
+ <div className="settings-panel">
+ <div className="settings-tabs">
+ {tabs.map(tab => (
+ <div key={tab.title} className={'tab-control ' + (this.activeTab === tab.title ? 'active' : 'inactive')} onClick={action(() => (this.activeTab = tab.title))}>
+ {tab.title}
+ </div>
+ ))}
+ </div>
- <div className="settings-user">
- <div className="settings-username">{Doc.CurrentUserEmail}</div>
- <button className="logout-button" onClick={() => window.location.assign(Utils.prepend("/logout"))} >
- {CurrentUserUtils.GuestDashboard ? "Exit" : "Log Out"}
- </button>
+ <div className="settings-user">
+ <div className="settings-username">{Doc.CurrentUserEmail}</div>
+ <button className="logout-button" onClick={() => window.location.assign(Utils.prepend('/logout'))}>
+ {Doc.GuestDashboard ? 'Exit' : 'Log Out'}
+ </button>
+ </div>
</div>
- </div>
- <div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color="black" size={"lg"} />
- </div>
+ <div className="close-button" onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ </div>
- <div className="settings-content">
- {tabs.map(tab => <div key={tab.title} className={"tab-section " + (this.activeTab === tab.title ? "active" : "inactive")}>{tab.ele}</div>)}
+ <div className="settings-content">
+ {tabs.map(tab => (
+ <div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
+ {tab.ele}
+ </div>
+ ))}
+ </div>
</div>
- </div>;
-
+ );
}
render() {
- return <MainViewModal
- contents={this.settingsInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: "500px", height: "300px", background: Cast(Doc.SharingDoc().userColor, "string", null) }} />;
+ return (
+ <MainViewModal
+ contents={this.settingsInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ closeOnExternalClick={this.close}
+ dialogueBoxStyle={{ width: '500px', height: '300px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }}
+ />
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 6d7f7e8df..895bd3374 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,29 +1,29 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { intersection } from "lodash";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import Select from "react-select";
-import * as RequestPromise from "request-promise";
-import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { Cast, NumCast, StrCast } from "../../fields/Types";
-import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util";
-import { Utils } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { CollectionView } from "../views/collections/CollectionView";
-import { DictationOverlay } from "../views/DictationOverlay";
-import { MainViewModal } from "../views/MainViewModal";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import { SearchBox } from "../views/search/SearchBox";
-import { CurrentUserUtils } from "./CurrentUserUtils";
-import { DocumentManager } from "./DocumentManager";
-import { GroupManager, UserOptions } from "./GroupManager";
-import { GroupMemberView } from "./GroupMemberView";
-import { SelectionManager } from "./SelectionManager";
-import "./SharingManager.scss";
-import { LinkManager } from "./LinkManager";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { intersection } from 'lodash';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import Select from 'react-select';
+import * as RequestPromise from 'request-promise';
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
+import { Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
+import { CollectionView } from '../views/collections/CollectionView';
+import { DictationOverlay } from '../views/DictationOverlay';
+import { MainViewModal } from '../views/MainViewModal';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import { SearchBox } from '../views/search/SearchBox';
+import { DocumentManager } from './DocumentManager';
+import { GroupManager, UserOptions } from './GroupManager';
+import { GroupMemberView } from './GroupMemberView';
+import { SelectionManager } from './SelectionManager';
+import './SharingManager.scss';
+import { LinkManager } from './LinkManager';
+import { Id } from '../../fields/FieldSymbols';
export interface User {
email: string;
@@ -44,19 +44,19 @@ interface GroupedOptions {
// const DefaultColor = "black";
// used to differentiate between individuals and groups when sharing
-const indType = "!indType/";
-const groupType = "!groupType/";
+const indType = '!indType/';
+const groupType = '!groupType/';
-const storage = "data";
+const storage = 'data';
/**
* A user who also has a sharing doc.
*/
interface ValidatedUser {
- user: User; // database minimal info to identify / communicate with a user (email, sharing doc id)
- sharingDoc: Doc; // document to share/message another user
+ user: User; // database minimal info to identify / communicate with a user (email, sharing doc id)
+ sharingDoc: Doc; // document to share/message another user
linkDatabase: Doc;
- userColor: string; // stored on the sharinDoc, extracted for convenience?
+ userColor: string; // stored on the sharinDoc, extracted for convenience?
}
@observer
@@ -71,8 +71,8 @@ export class SharingManager extends React.Component<{}> {
@observable private overlayOpacity = 0.4; // for the modal
@observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
@observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
- @observable private individualSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of individuals
- @observable private groupSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of groups
+ @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
+ @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups
private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
private distributeAclsButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the distribute button, used for the position of the popup
// if both showUserOptions and showGroupOptions are false then both are displayed
@@ -89,7 +89,7 @@ export class SharingManager extends React.Component<{}> {
[AclAugment, SharingPermissions.Augment],
[AclSelfEdit, SharingPermissions.SelfEdit],
[AclEdit, SharingPermissions.Edit],
- [AclAdmin, SharingPermissions.Admin]
+ [AclAdmin, SharingPermissions.Admin],
]);
// private get linkVisible() {
@@ -105,17 +105,20 @@ export class SharingManager extends React.Component<{}> {
this.isOpen = this.targetDoc !== undefined;
this.permissions = SharingPermissions.Augment;
});
- }
+ };
public close = action(() => {
this.isOpen = false;
this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
TaskCompletionBox.taskCompleted = false;
- setTimeout(action(() => {
- // this.copied = false;
- DictationOverlay.Instance.hasActiveModal = false;
- this.targetDoc = undefined;
- }), 500);
+ setTimeout(
+ action(() => {
+ // this.copied = false;
+ DictationOverlay.Instance.hasActiveModal = false;
+ this.targetDoc = undefined;
+ }),
+ 500
+ );
});
constructor(props: {}) {
@@ -134,9 +137,9 @@ export class SharingManager extends React.Component<{}> {
* Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
*/
populateUsers = async () => {
- if (!this.populating) {
+ if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') {
this.populating = true;
- const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+ const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
const sharingDocs: ValidatedUser[] = [];
const evaluating = raw.map(async user => {
@@ -146,7 +149,8 @@ export class SharingManager extends React.Component<{}> {
const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId);
if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
await DocListCastAsync(linkDatabase.data);
- (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => { // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager
+ (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => {
+ // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager
const a1 = await Cast(link?.anchor1, Doc, null);
const a2 = await Cast(link?.anchor2, Doc, null);
});
@@ -166,7 +170,7 @@ export class SharingManager extends React.Component<{}> {
this.populating = false;
});
}
- }
+ };
/**
* Shares the document with a user.
@@ -176,74 +180,77 @@ export class SharingManager extends React.Component<{}> {
const target = targetDoc || this.targetDoc!;
const acl = `acl-${normalizeEmail(user.email)}`;
const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
- const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1;
+ const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
- return !docs.map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
-
- if (permission === SharingPermissions.None) {
- if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1;
- }
- else {
- if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
- }
+ return !docs
+ .map(doc => {
+ doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
+
+ if (permission === SharingPermissions.None) {
+ if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1;
+ } else {
+ if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
+ }
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
- this.setDashboardBackground(doc, permission as SharingPermissions);
- if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc);
- else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
- }).some(success => !success);
- }
+ this.setDashboardBackground(doc, permission as SharingPermissions);
+ if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc);
+ else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc);
+ })
+ .some(success => !success);
+ };
/**
* Sets the permission on the target for the group.
- * @param group
- * @param permission
+ * @param group
+ * @param permission
*/
setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
-
const target = targetDoc || this.targetDoc!;
const key = normalizeEmail(StrCast(group.title));
const acl = `acl-${key}`;
- const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1;
+ const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
// ! ensures it returns true if document has been shared successfully, false otherwise
- return !docs.map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
+ return !docs
+ .map(doc => {
+ doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
+
+ if (permission === SharingPermissions.None) {
+ if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1;
+ } else {
+ if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1;
+ }
- if (permission === SharingPermissions.None) {
- if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1;
- }
- else {
- if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1;
- }
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
+ this.setDashboardBackground(doc, permission as SharingPermissions);
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
- this.setDashboardBackground(doc, permission as SharingPermissions);
+ if (group instanceof Doc) {
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
- if (group instanceof Doc) {
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
-
- // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc
- group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]);
+ // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc
+ group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : (group.docsShared = new List<Doc>([doc]));
- return users.map(({ user, sharingDoc }) => {
- if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added
- else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists
- }).some(success => !success);
- }
- }).some(success => success);
- }
+ return users
+ .map(({ user, sharingDoc }) => {
+ if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc); // remove the doc from the list if it already exists
+ })
+ .some(success => !success);
+ }
+ })
+ .some(success => success);
+ };
/**
* Shares the documents shared with a group with a new user who has been added to that group.
- * @param group
- * @param emailId
+ * @param group
+ * @param emailId
*/
shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
const user = this.users.find(({ user: { email } }) => email === emailId)!;
@@ -255,54 +262,53 @@ export class SharingManager extends React.Component<{}> {
DocListCastAsync(group.docsShared).then(dl => {
const filtered = dl?.filter(doc => !userdocs?.includes(doc));
filtered && userdocs?.push(...filtered);
- }));
+ })
+ );
}
}
- }
+ };
/**
* Called from the properties sidebar to change permissions of a user.
*/
shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => {
- if (shareWith !== "Public" && shareWith !== "Override") {
- const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith));
+ if (shareWith !== 'Public' && shareWith !== 'Override') {
+ const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith));
docs.forEach(doc => {
if (user) this.setInternalSharing(user, permission, doc);
else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc);
});
- }
- else {
- const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+ } else {
+ const dashboards = DocListCast(Doc.MyDashboards.data);
docs.forEach(doc => {
const isDashboard = dashboards.indexOf(doc) !== -1;
if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
});
}
- }
+ };
/**
* Sets the background of the Dashboard if it has been shared as a visual indicator
*/
setDashboardBackground = async (doc: Doc, permission: SharingPermissions) => {
- if (Doc.IndexOf(doc, DocListCast(CurrentUserUtils.MyDashboards.data)) !== -1) {
+ if (Doc.IndexOf(doc, DocListCast(Doc.MyDashboards.data)) !== -1) {
if (permission !== SharingPermissions.None) {
doc.isShared = true;
- doc.backgroundColor = "green";
- }
- else {
+ doc.backgroundColor = 'green';
+ } else {
const acls = doc[DataSym][AclSym];
- if (Object.keys(acls).every(key => key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key]))) {
+ if (Object.keys(acls).every(key => (key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key])))) {
doc.isShared = undefined;
doc.backgroundColor = undefined;
}
}
}
- }
+ };
/**
* Removes the documents shared with a user through a group when the user is removed from the group.
- * @param group
- * @param emailId
+ * @param group
+ * @param emailId
*/
removeMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
@@ -315,15 +321,15 @@ export class SharingManager extends React.Component<{}> {
})
);
}
- }
+ };
/**
* Removes a group's permissions from documents that have been shared with it.
- * @param group
+ * @param group
*/
removeGroup = (group: Doc) => {
if (group.docsShared) {
- const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+ const dashboards = DocListCast(Doc.MyDashboards.data);
DocListCast(group.docsShared).forEach(doc => {
const acl = `acl-${StrCast(group.title)}`;
const isDashboard = dashboards.indexOf(doc) !== -1;
@@ -335,9 +341,7 @@ export class SharingManager extends React.Component<{}> {
users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
});
}
- }
-
-
+ };
// private setExternalSharing = (permission: string) => {
// const targetDoc = this.targetDoc;
@@ -367,28 +371,28 @@ export class SharingManager extends React.Component<{}> {
*/
private sharingOptions(uniform: boolean, override?: boolean) {
const dropdownValues: string[] = Object.values(SharingPermissions);
- if (!uniform) dropdownValues.unshift("-multiple-");
- if (override) dropdownValues.unshift("None");
- return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
- (
+ if (!uniform) dropdownValues.unshift('-multiple-');
+ if (override) dropdownValues.unshift('None');
+ return dropdownValues
+ .filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any))
+ .map(permission => (
<option key={permission} value={permission}>
{permission}
</option>
- )
- );
+ ));
}
private focusOn = (contents: string) => {
- const title = this.targetDoc ? StrCast(this.targetDoc.title) : "";
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc];
return (
<span
- className={"focus-span"}
+ className={'focus-span'}
title={title}
onClick={() => {
let context: Opt<CollectionView>;
if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
- DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document);
+ DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, [context.props.Document]);
}
}}
onPointerEnter={action(() => {
@@ -404,12 +408,11 @@ export class SharingManager extends React.Component<{}> {
this.dialogueBoxOpacity = 1;
this.overlayOpacity = 0.4;
}
- })}
- >
+ })}>
{contents}
</span>
);
- }
+ };
/**
* Handles changes in the users selected in react-select
@@ -417,7 +420,7 @@ export class SharingManager extends React.Component<{}> {
@action
handleUsersChange = (selectedOptions: any) => {
this.selectedUsers = selectedOptions as UserOptions[];
- }
+ };
/**
* Handles changes in the permission chosen to share with someone with
@@ -425,7 +428,7 @@ export class SharingManager extends React.Component<{}> {
@action
handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
this.permissions = event.currentTarget.value as SharingPermissions;
- }
+ };
/**
* Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
@@ -436,8 +439,7 @@ export class SharingManager extends React.Component<{}> {
this.selectedUsers.forEach(user => {
if (user.value.includes(indType)) {
this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
- }
- else {
+ } else {
this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
}
});
@@ -445,13 +447,16 @@ export class SharingManager extends React.Component<{}> {
const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();
TaskCompletionBox.popupX = left - 1.5 * width;
TaskCompletionBox.popupY = top - 1.5 * height;
- TaskCompletionBox.textDisplayed = "Document shared!";
+ TaskCompletionBox.textDisplayed = 'Document shared!';
TaskCompletionBox.taskCompleted = true;
- setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2000
+ );
this.selectedUsers = null;
}
- }
+ };
// distributeOverCollection = (targetDoc?: Doc) => {
// const target = targetDoc || this.targetDoc!;
@@ -471,7 +476,7 @@ export class SharingManager extends React.Component<{}> {
const { email: e1 } = u1.user;
const { email: e2 } = u2.user;
return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
- }
+ };
/**
* Sorting algorithm to sort groups.
@@ -480,7 +485,7 @@ export class SharingManager extends React.Component<{}> {
const g1 = StrCast(group1.title);
const g2 = StrCast(group2.title);
return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
- }
+ };
/**
* @returns the main interface of the SharingManager.
@@ -488,28 +493,29 @@ export class SharingManager extends React.Component<{}> {
@computed get sharingInterface() {
TraceMobx();
const groupList = GroupManager.Instance?.allGroups || [];
- const sortedUsers = this.users.slice().sort(this.sortUsers).map(({ user: { email } }) => ({ label: email, value: indType + email }));
- const sortedGroups = groupList.slice().sort(this.sortGroups).map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) }));
+ const sortedUsers = this.users
+ .slice()
+ .sort(this.sortUsers)
+ .map(({ user: { email } }) => ({ label: email, value: indType + email }));
+ const sortedGroups = groupList
+ .slice()
+ .sort(this.sortGroups)
+ .map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) }));
// the next block handles the users shown (individuals/groups/both)
const options: GroupedOptions[] = [];
if (GroupManager.Instance) {
if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) {
- options.push(
- { label: 'Individuals', options: sortedUsers },
- { label: 'Groups', options: sortedGroups });
- }
- else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers });
+ options.push({ label: 'Individuals', options: sortedUsers }, { label: 'Groups', options: sortedGroups });
+ } else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers });
else options.push({ label: 'Groups', options: sortedGroups });
}
- const users = this.individualSort === "ascending" ? this.users.slice().sort(this.sortUsers) : this.individualSort === "descending" ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
- const groups = this.groupSort === "ascending" ? groupList.slice().sort(this.sortGroups) : this.groupSort === "descending" ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
+ const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
+ const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
// handles the case where multiple documents are selected
- let docs = SelectionManager.Views().length < 2 ?
- [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]]
- : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]);
+ let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]));
if (this.myDocAcls) {
const newDocs: Doc[] = [];
@@ -524,209 +530,174 @@ export class SharingManager extends React.Component<{}> {
const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin);
// users in common between all docs
- const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])));
+ const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym]))));
// the list of users shared with
- const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, linkDatabase, sharingDoc, userColor }) => {
- const userKey = `acl-${normalizeEmail(user.email)}`;
- const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]);
- const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-";
-
- return !permissions ? (null) : (
- <div
- key={userKey}
- className={"container"}
- >
- <span className={"padding"}>{user.email}</span>
- <div className="edit-actions">
- {admin || this.myDocAcls ? (
- <select
- className={"permissions-dropdown"}
- value={permissions}
- onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}
- >
- {this.sharingOptions(uniform)}
- </select>
- ) : (
- <div className={"permissions-dropdown"}>
- {permissions}
- </div>
+ const userListContents: (JSX.Element | null)[] = users
+ .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ .map(({ user, linkDatabase, sharingDoc, userColor }) => {
+ const userKey = `acl-${normalizeEmail(user.email)}`;
+ const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]));
+ const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+
+ return !permissions ? null : (
+ <div key={userKey} className={'container'}>
+ <span className={'padding'}>{user.email}</span>
+ <div className="edit-actions">
+ {admin || this.myDocAcls ? (
+ <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}>
+ {this.sharingOptions(uniform)}
+ </select>
+ ) : (
+ <div className={'permissions-dropdown'}>{permissions}</div>
)}
+ </div>
</div>
- </div>
- );
- });
+ );
+ });
// checks if every doc has the same author
const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
// the owner of the doc and the current user are placed at the top of the user list.
userListContents.unshift(
- sameAuthor ?
- (
- <div
- key={"owner"}
- className={"container"}
- >
- <span className={"padding"}>{targetDoc?.author === Doc.CurrentUserEmail ? "Me" : targetDoc?.author}</span>
- <div className="edit-actions">
- <div className={"permissions-dropdown"}>
- Owner
- </div>
- </div>
+ sameAuthor ? (
+ <div key={'owner'} className={'container'}>
+ <span className={'padding'}>{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : targetDoc?.author}</span>
+ <div className="edit-actions">
+ <div className={'permissions-dropdown'}>Owner</div>
</div>
- ) : null,
- sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ?
- (
- <div
- key={"me"}
- className={"container"}
- >
- <span className={"padding"}>Me</span>
- <div className="edit-actions">
- <div className={"permissions-dropdown"}>
- {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : "-multiple-"}
- </div>
- </div>
+ </div>
+ ) : null,
+ sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? (
+ <div key={'me'} className={'container'}>
+ <span className={'padding'}>Me</span>
+ <div className="edit-actions">
+ <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : '-multiple-'}</div>
</div>
- ) : null
+ </div>
+ ) : null
);
-
// the list of groups shared with
- const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true);
- groupListMap.unshift({ title: "Public" });//, { title: "ALL" });
+ const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
+ groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" });
const groupListContents = groupListMap.map(group => {
const groupKey = `acl-${StrCast(group.title)}`;
- const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
- const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : "-multiple-";
-
- return !permissions ? (null) : (
- <div
- key={groupKey}
- className={"container"}
- >
- <div className={"padding"}>{group.title}</div>
- {group instanceof Doc ?
- (<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
- <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
- </div>)
- : (null)}
+ const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]));
+ const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
+
+ return !permissions ? null : (
+ <div key={groupKey} className={'container'}>
+ <div className={'padding'}>{StrCast(group.title)}</div>
+ {group instanceof Doc ? (
+ <div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}>
+ <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
+ </div>
+ ) : null}
<div className="edit-actions">
{admin || this.myDocAcls ? (
- <select
- className={"permissions-dropdown"}
- value={permissions}
- onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
- >
- {this.sharingOptions(uniform, group.title === "Override")}
+ <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
+ {this.sharingOptions(uniform, group.title === 'Override')}
</select>
) : (
- <div className={"permissions-dropdown"}>
- {permissions}
- </div>
- )}
+ <div className={'permissions-dropdown'}>{permissions}</div>
+ )}
</div>
</div>
);
});
return (
- <div className={"sharing-interface"}>
- {GroupManager.Instance?.currentGroup ?
- <GroupMemberView
- group={GroupManager.Instance.currentGroup}
- onCloseButtonClick={action(() => GroupManager.Instance.currentGroup = undefined)}
- /> :
- null}
+ <div className={'sharing-interface'}>
+ {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
<div className="sharing-contents">
- <p className={"share-title"}><b>Share </b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}</p>
- <div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
+ <p className={'share-title'}>
+ <b>Share </b>
+ {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
+ </p>
+ <div className={'close-button'} onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
{/* {this.linkVisible ?
<div>
{this.sharingUrl}
</div> :
(null)} */}
- {<div className="share-container">
- <div className="share-setup">
- <Select
- className="user-search"
- placeholder="Enter user or group name..."
- isMulti
- isSearchable
- closeMenuOnSelect={false}
- options={options}
- onChange={this.handleUsersChange}
- value={this.selectedUsers}
- styles={{
- indicatorSeparator: () => ({
- visibility: "hidden"
- })
- }}
- />
- <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
- {this.sharingOptions(true)}
- </select>
- <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
- Share
- </button>
- </div>
- <div className="sort-checkboxes">
- <input type="checkbox" onChange={action(() => this.showUserOptions = !this.showUserOptions)} /> <label style={{ marginRight: 10 }}>Individuals</label>
- <input type="checkbox" onChange={action(() => this.showGroupOptions = !this.showGroupOptions)} /> <label>Groups</label>
- </div>
+ {
+ <div className="share-container">
+ <div className="share-setup">
+ <Select
+ className="user-search"
+ placeholder="Enter user or group name..."
+ isMulti
+ isSearchable
+ closeMenuOnSelect={false}
+ options={options}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={this.handleUsersChange}
+ value={this.selectedUsers}
+ styles={{
+ indicatorSeparator: () => ({
+ visibility: 'hidden',
+ }),
+ }}
+ />
+ <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
+ {this.sharingOptions(true)}
+ </select>
+ <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
+ Share
+ </button>
+ </div>
+ <div className="sort-checkboxes">
+ <input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label>
+ <input type="checkbox" onChange={action(() => (this.showGroupOptions = !this.showGroupOptions))} /> <label>Groups</label>
+ </div>
- <div className="acl-container">
- {Doc.UserDoc().noviceMode ? (null) :
- <div className="layoutDoc-acls">
- <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label>
- </div>}
+ <div className="acl-container">
+ {Doc.noviceMode ? null : (
+ <div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ </div>
+ )}
+ </div>
</div>
- </div>
}
<div className="main-container">
- <div className={"individual-container"}>
- <div
- className="user-sort"
- onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}>
- Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
- : this.individualSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
- : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
- </div>
- <div className={"users-list"}>
- {userListContents}
+ <div className={'individual-container'}>
+ <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
+ Individuals{' '}
+ {this.individualSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.individualSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
</div>
+ <div className={'users-list'}>{userListContents}</div>
</div>
- <div className={"group-container"}>
- <div
- className="user-sort"
- onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
- : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
- : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
-
- </div>
- <div className={"groups-list"}>
- {groupListContents}
+ <div className={'group-container'}>
+ <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ Groups{' '}
+ {this.groupSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.groupSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
</div>
+ <div className={'groups-list'}>{groupListContents}</div>
</div>
</div>
-
</div>
</div>
);
}
render() {
- return <MainViewModal
- contents={this.sharingInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
- overlayDisplayedOpacity={this.overlayOpacity}
- closeOnExternalClick={this.close}
- />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 069f81d38..2c0a1da8b 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,8 +1,8 @@
-import { observable, action, runInAction } from "mobx";
-import { computedFn } from "mobx-utils";
+import { observable, action, runInAction } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { Doc } from '../../fields/Doc';
export namespace SnappingManager {
-
class Manager {
@observable IsDragging: boolean = false;
@observable public horizSnapLines: number[] = [];
@@ -15,25 +15,34 @@ export namespace SnappingManager {
this.horizSnapLines = horizLines;
this.vertSnapLines = vertLines;
}
-
- @observable cachedGroups: string[] = [];
- @action setCachedGroups(groups: string[]) { this.cachedGroups = groups; }
}
const manager = new Manager();
- export function clearSnapLines() { manager.clearSnapLines(); }
- export function setSnapLines(horizLines: number[], vertLines: number[]) { manager.setSnapLines(horizLines, vertLines); }
- export function horizSnapLines() { return manager.horizSnapLines; }
- export function vertSnapLines() { return manager.vertSnapLines; }
+ export function clearSnapLines() {
+ manager.clearSnapLines();
+ }
+ export function setSnapLines(horizLines: number[], vertLines: number[]) {
+ manager.setSnapLines(horizLines, vertLines);
+ }
+ export function horizSnapLines() {
+ return manager.horizSnapLines;
+ }
+ export function vertSnapLines() {
+ return manager.vertSnapLines;
+ }
- export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
- export function GetIsDragging() { return manager.IsDragging; }
+ export function SetIsDragging(dragging: boolean) {
+ runInAction(() => (manager.IsDragging = dragging));
+ }
+ export function GetIsDragging() {
+ return manager.IsDragging;
+ }
- /// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts
- // need to investigate further what caused the mobx update problems and move to a better location.
- const getCachedGroupByNameCache = computedFn(function (name: string) { return manager.cachedGroups.includes(name); }, true);
- export function GetCachedGroupByName(name: string) { return getCachedGroupByNameCache(name); }
- export function SetCachedGroups(groups: string[]) { manager.setCachedGroups(groups); }
+ export function SetShowSnapLines(show: boolean) {
+ runInAction(() => (Doc.UserDoc().showSnapLines = show));
+ }
+ export function GetShowSnapLines() {
+ return Doc.UserDoc().showSnapLines;
+ }
}
-
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
new file mode 100644
index 000000000..4a2ccd706
--- /dev/null
+++ b/src/client/util/TrackMovements.ts
@@ -0,0 +1,270 @@
+import { IReactionDisposer, observable, observe, reaction } from 'mobx';
+import { NumCast } from '../../fields/Types';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { Id } from '../../fields/FieldSymbols';
+
+export type Movement = {
+ time: number;
+ panX: number;
+ panY: number;
+ scale: number;
+ docId: string;
+};
+
+export type Presentation = {
+ movements: Movement[] | null;
+ totalTime: number;
+ meta: Object | Object[];
+};
+
+export class TrackMovements {
+ private static get NULL_PRESENTATION(): Presentation {
+ return { movements: null, meta: {}, totalTime: -1 };
+ }
+
+ // instance variables
+ private currentPresentation: Presentation;
+ private tracking: boolean;
+ private absoluteStart: number;
+ // instance variable for holding the FFViews and their disposers
+ private recordingFFViews: Map<string, IReactionDisposer> | null;
+ private tabChangeDisposeFunc: IReactionDisposer | null;
+
+ // create static instance and getter for global use
+ @observable static _instance: TrackMovements;
+ static get Instance(): TrackMovements {
+ return TrackMovements._instance;
+ }
+ constructor() {
+ // init the global instance
+ TrackMovements._instance = this;
+
+ // init the instance variables
+ this.currentPresentation = TrackMovements.NULL_PRESENTATION;
+ this.tracking = false;
+ this.absoluteStart = -1;
+
+ // used for tracking movements in the view frame
+ this.recordingFFViews = null;
+ this.tabChangeDisposeFunc = null;
+ }
+
+ // little helper :)
+ private get nullPresentation(): boolean {
+ return this.currentPresentation.movements === null;
+ }
+
+ private addRecordingFFView(doc: Doc, key: string = doc[Id]): void {
+ // console.info('adding dispose func : docId', key, 'doc', doc);
+
+ if (this.recordingFFViews === null) {
+ console.warn('addFFView on null RecordingApi');
+ return;
+ }
+ if (this.recordingFFViews.has(key)) {
+ console.warn('addFFView : key already in map');
+ return;
+ }
+
+ const disposeFunc = reaction(
+ () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }),
+ res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale)
+ );
+ this.recordingFFViews?.set(key, disposeFunc);
+ }
+
+ private removeRecordingFFView = (key: string) => {
+ // console.info('removing dispose func : docId', key);
+ if (this.recordingFFViews === null) {
+ console.warn('removeFFView on null RecordingApi');
+ return;
+ }
+ this.recordingFFViews.get(key)?.();
+ this.recordingFFViews.delete(key);
+ };
+
+ // in the case where only one tab was changed (updates not across dashboards), set only one to true
+ private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => {
+ if (this.recordingFFViews === null) return;
+
+ // so that the size comparisons are correct, we must filter to only the FFViews
+ const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform';
+ const tabbedFFViews = new Set<string>();
+ for (const DashDoc of tabbedDocs) {
+ if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]);
+ }
+
+ // new tab was added - need to add it
+ if (tabbedFFViews.size > this.recordingFFViews.size) {
+ for (const DashDoc of tabbedDocs) {
+ if (!this.recordingFFViews.has(DashDoc[Id])) {
+ if (isFFView(DashDoc)) {
+ this.addRecordingFFView(DashDoc);
+
+ // only one max change, so return
+ if (onlyOne) return;
+ }
+ }
+ }
+ }
+ // tab was removed - need to remove it from recordingFFViews
+ else if (tabbedFFViews.size < this.recordingFFViews.size) {
+ for (const [key] of this.recordingFFViews) {
+ if (!tabbedFFViews.has(key)) {
+ this.removeRecordingFFView(key);
+ if (onlyOne) return;
+ }
+ }
+ }
+ };
+
+ private initTabTracker = () => {
+ if (this.recordingFFViews === null) {
+ this.recordingFFViews = new Map();
+ }
+
+ // init the dispose funcs on the page
+ const docList = DocListCast(CollectionDockingView.Instance?.props.Document.data);
+ this.updateRecordingFFViewsFromTabs(docList);
+
+ // create a reaction to monitor changes in tabs
+ this.tabChangeDisposeFunc = reaction(
+ () => CollectionDockingView.Instance?.props.Document.data,
+ change => {
+ // TODO: consider changing between dashboards
+ // console.info('change in tabs', change);
+ this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
+ }
+ );
+ };
+
+ start = (meta?: Object) => {
+ this.initTabTracker();
+
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'recording';
+
+ // (1a) get start date for presenation
+ const startDate = new Date();
+ // (1b) set start timestamp to absolute timestamp
+ this.absoluteStart = startDate.getTime();
+
+ // (2) assign meta content if it exists
+ this.currentPresentation.meta = meta || {};
+ // (3) assign start date to currentPresenation
+ this.currentPresentation.movements = [];
+ // (4) set tracking true to allow trackMovements
+ this.tracking = true;
+ };
+
+ /* stops the video and returns the presentatation; if no presentation, returns undefined */
+ yieldPresentation(clearData: boolean = true): Presentation | null {
+ // if no presentation or done tracking, return null
+ if (this.nullPresentation || !this.tracking) return null;
+
+ // set the previus recording view to the play view
+ // this.playFFView = this.recordingFFView;
+
+ // ensure we add the endTime now that they are done recording
+ const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart };
+
+ // reset the current presentation
+ clearData && this.clear();
+
+ // console.info('yieldPresentation', cpy);
+ return cpy;
+ }
+
+ finish = (): void => {
+ // make is tracking false
+ this.tracking = false;
+ // reset the RecordingApi instance
+ this.clear();
+ };
+
+ private clear = (): void => {
+ // clear the disposeFunc if we are done (not tracking)
+ if (!this.tracking) {
+ this.removeAllRecordingFFViews();
+ this.tabChangeDisposeFunc?.();
+ // update the presentation mode now that we are done tracking
+ Doc.UserDoc().presentationMode = 'none';
+
+ this.recordingFFViews = null;
+ this.tabChangeDisposeFunc = null;
+ }
+
+ // clear presenation data
+ this.currentPresentation = TrackMovements.NULL_PRESENTATION;
+ // clear absoluteStart
+ this.absoluteStart = -1;
+ };
+
+ removeAllRecordingFFViews = () => {
+ if (this.recordingFFViews === null) {
+ console.warn('removeAllFFViews on null RecordingApi');
+ return;
+ }
+
+ for (const [id, disposeFunc] of this.recordingFFViews) {
+ // console.info('calling dispose func : docId', id);
+ disposeFunc();
+ this.recordingFFViews.delete(id);
+ }
+ };
+
+ private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => {
+ // ensure we are recording to track
+ if (!this.tracking) {
+ console.error('[recordingApi.ts] trackMovements(): tracking is false');
+ return;
+ }
+ // check to see if the presetation is init - if not, we are between segments
+ // TODO: make this more clear - tracking should be "live tracking", not always true when the recording api being used (between start and yieldPres)
+ // bacuse tracking should be false inbetween segments high key
+ if (this.nullPresentation) {
+ console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments');
+ return;
+ }
+
+ // get the time
+ const time = new Date().getTime() - this.absoluteStart;
+ // make new movement object
+ const movement: Movement = { time, panX, panY, scale, docId };
+
+ // add that movement to the current presentation data's movement array
+ this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
+ };
+
+ // method that concatenates an array of presentatations into one
+ public concatPresentations = (presentations: Presentation[]): Presentation => {
+ // these three will lead to the combined presentation
+ let combinedMovements: Movement[] = [];
+ let sumTime = 0;
+ let combinedMetas: any[] = [];
+
+ presentations.forEach(presentation => {
+ const { movements, totalTime, meta } = presentation;
+
+ // update movements if they had one
+ if (movements) {
+ // add the summed time to the movements
+ const addedTimeMovements = movements.map(move => {
+ return { ...move, time: move.time + sumTime };
+ });
+ // concat the movements already in the combined presentation with these new ones
+ combinedMovements.push(...addedTimeMovements);
+ }
+
+ // update the totalTime
+ sumTime += totalTime;
+
+ // concatenate the metas
+ combinedMetas.push(meta);
+ });
+
+ // return the combined presentation with the updated total summed time
+ return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
+ };
+}
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index d1f1a0099..d0aec45a6 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,5 +1,5 @@
-import { observable, action, runInAction } from "mobx";
-import { Without } from "../../Utils";
+import { observable, action, runInAction } from 'mobx';
+import { Without } from '../../Utils';
function getBatchName(target: any, key: string | symbol): string {
const keyName = key.toString();
@@ -28,9 +28,9 @@ function propertyDecorator(target: any, key: string | symbol) {
} finally {
batch.end();
}
- }
+ },
});
- }
+ },
});
}
@@ -39,7 +39,7 @@ export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
if (!key) {
return function () {
- const batch = UndoManager.StartBatch("");
+ const batch = UndoManager.StartBatch('');
try {
return target.apply(undefined, arguments);
} finally {
@@ -96,7 +96,7 @@ export namespace UndoManager {
}
export function PrintBatches(): void {
- console.log("Open Undo Batches:");
+ console.log('Open Undo Batches:');
GetOpenBatches().forEach(batch => console.log(batch.batchName));
}
@@ -106,23 +106,25 @@ export namespace UndoManager {
}
export function FilterBatches(fieldTypes: string[]) {
const fieldCounts: { [key: string]: number } = {};
- const lastStack = UndoManager.undoStack.slice(-1)[0];//.lastElement();
+ const lastStack = UndoManager.undoStack.slice(-1)[0]; //.lastElement();
if (lastStack) {
lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1));
const fieldCount2: { [key: string]: number } = {};
- runInAction(() =>
- UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
- if (fieldTypes.includes(ev.prop)) {
- fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
- if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
- return false;
- }
- return true;
- }));
+ runInAction(
+ () =>
+ (UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
+ if (fieldTypes.includes(ev.prop)) {
+ fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
+ if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
+ return false;
+ }
+ return true;
+ }))
+ );
}
}
export function TraceOpenBatches() {
- console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`);
+ console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join('\n\t')}\n`);
}
export class Batch {
private disposed: boolean = false;
@@ -133,15 +135,15 @@ export namespace UndoManager {
private dispose = (cancel: boolean) => {
if (this.disposed) {
- throw new Error("Cannot dispose an already disposed batch");
+ throw new Error('Cannot dispose an already disposed batch');
}
this.disposed = true;
openBatches.splice(openBatches.indexOf(this));
- EndBatch(cancel);
- }
+ return EndBatch(cancel);
+ };
- end = () => { this.dispose(false); };
- cancel = () => { this.dispose(true); };
+ end = () => this.dispose(false);
+ cancel = () => this.dispose(true);
}
export function StartBatch(batchName: string): Batch {
@@ -163,7 +165,9 @@ export namespace UndoManager {
}
redoStack.length = 0;
currentBatch = undefined;
+ return true;
}
+ return false;
});
export function RunInTempBatch<T>(fn: () => T) {
@@ -232,5 +236,4 @@ export namespace UndoManager {
undoStack.push(commands);
});
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/AudioWaveform.scss
index e20434a25..6cbd1759a 100644
--- a/src/client/views/AudioWaveform.scss
+++ b/src/client/views/AudioWaveform.scss
@@ -1,7 +1,7 @@
.audioWaveform {
position: relative;
width: 100%;
- height: 100%;
+ height: 200%;
overflow: hidden;
z-index: -1000;
bottom: 0;
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 8f3b7c2cd..525c0ce5a 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -1,126 +1,119 @@
import React = require("react");
import axios from "axios";
-import { action, computed } from "mobx";
+import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import Waveform from "react-audio-waveform";
import { Doc } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
-import { Cast, NumCast } from "../../fields/Types";
+import { Cast } from "../../fields/Types";
import { numberRange } from "../../Utils";
import "./AudioWaveform.scss";
import { Colors } from "./global/globalEnums";
+
+/**
+ * AudioWaveform
+ *
+ * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
+ * Uses react-audio-waveform package.
+ * Bins the audio data into audioBuckets which are passed to package to render the lines.
+ * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
+ */
+
+
export interface AudioWaveformProps {
- duration: number;
+ duration: number; // length of media clip
+ rawDuration: number; // length of underlying media data
mediaPath: string;
layoutDoc: Doc;
- trimming: boolean;
- PanelHeight: () => number;
+ clipStart: number;
+ clipEnd: number;
+ zoomFactor: number;
+ PanelHeight: number;
+ PanelWidth: number;
}
@observer
export class AudioWaveform extends React.Component<AudioWaveformProps> {
- public static NUMBER_OF_BUCKETS = 100;
- @computed get _waveHeight() {
- return Math.max(50, this.props.PanelHeight());
+ public static NUMBER_OF_BUCKETS = 100; // number of buckets data is divided into to draw waveform lines
+
+ _disposer: IReactionDisposer | undefined;
+
+ @computed get waveHeight() { return Math.max(50, this.props.PanelHeight); }
+
+ @computed get clipStart() { return this.props.clipStart; }
+ @computed get clipEnd() { return this.props.clipEnd; }
+ @computed get zoomFactor() { return this.props.zoomFactor; }
+
+ @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec("number"), []); }
+ audioBucketField = (start: number, end: number, zoomFactor: number) => "audioBuckets/" + "/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_") + "/" + (zoomFactor * 10);
+
+
+ componentWillUnmount() {
+ this._disposer?.();
}
+
componentDidMount() {
- const audioBuckets = Cast(
- this.props.layoutDoc.audioBuckets,
- listSpec("number"),
- []
- );
- if (!audioBuckets.length) {
- this.props.layoutDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
- setTimeout(this.createWaveformBuckets);
- }
+ this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
+ ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
+ if (!this.props.layoutDoc[fieldKey]) {
+ // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
+ const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec("number"));
+ this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice(clipStart / this.props.rawDuration * waveform.length, clipEnd / this.props.rawDuration * waveform.length));
+ setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
+ }
+ }, { fireImmediately: true });
}
// decodes the audio file into peaks for generating the waveform
- createWaveformBuckets = async () => {
+ createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
(response) => {
const context = new window.AudioContext();
context.decodeAudioData(
response.data,
action((buffer) => {
- const decodedAudioData = buffer.getChannelData(0);
+ const rawDecodedAudioData = buffer.getChannelData(0);
+ const startInd = clipStart / this.props.rawDuration;
+ const endInd = clipEnd / this.props.rawDuration;
+ const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
+ const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
const bucketDataSize = Math.floor(
- decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS
+ decodedAudioData.length / numBuckets
);
const brange = Array.from(Array(bucketDataSize));
- this.props.layoutDoc.audioBuckets = new List<number>(
- numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map(
- (i: number) =>
- brange.reduce(
- (p, x, j) =>
- Math.abs(
- Math.max(p, decodedAudioData[i * bucketDataSize + j])
- ),
- 0
- ) / 2
- )
+ const bucketList = numberRange(numBuckets).map(
+ (i: number) =>
+ brange.reduce(
+ (p, x, j) =>
+ Math.abs(
+ Math.max(p, decodedAudioData[i * bucketDataSize + j])
+ ),
+ 0
+ ) / 2
);
+ this.props.layoutDoc[fieldKey] = new List<number>(bucketList);
})
);
}
);
}
- @action
- createTrimBuckets = () => {
- const audioBuckets = Cast(
- this.props.layoutDoc.audioBuckets,
- listSpec("number"),
- []
- );
-
- const start = Math.floor(
- (NumCast(this.props.layoutDoc.clipStart) / this.props.duration) * 100
- );
- const end = Math.floor(
- (NumCast(this.props.layoutDoc.clipEnd) / this.props.duration) * 100
- );
- return audioBuckets.slice(start, end);
- }
-
render() {
- const audioBuckets = Cast(
- this.props.layoutDoc.audioBuckets,
- listSpec("number"),
- []
- );
-
return (
<div className="audioWaveform">
- {this.props.trimming || !this.props.layoutDoc.clipEnd ? (
- <Waveform
- color={Colors.MEDIUM_BLUE}
- height={this._waveHeight}
- barWidth={0.1}
- pos={this.props.duration}
- duration={this.props.duration}
- peaks={
- audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS
- ? audioBuckets
- : undefined
- }
- progressColor={Colors.MEDIUM_BLUE}
- />
- ) : (
- <Waveform
- color={Colors.MEDIUM_BLUE}
- height={this._waveHeight}
- barWidth={0.1}
- pos={this.props.duration}
- duration={this.props.duration}
- peaks={this.createTrimBuckets()}
- progressColor={Colors.MEDIUM_BLUE}
- />
- )}
+ <Waveform
+ color={Colors.MEDIUM_BLUE_ALT}
+ height={this.waveHeight}
+ barWidth={200 / this.audioBuckets.length}
+ pos={this.props.duration}
+ duration={this.props.duration}
+ peaks={this.audioBuckets}
+ progressColor={Colors.MEDIUM_BLUE_ALT}
+ />
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index ea24dbf6d..1e6a377de 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,10 +1,10 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables';
.contextMenu-cont {
position: absolute;
display: flex;
z-index: 100000;
- box-shadow: 0px 3px 4px rgba(0,0,0,30%);
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
flex-direction: column;
background: whitesmoke;
border-radius: 3px;
@@ -28,9 +28,9 @@
position: absolute;
display: flex;
z-index: 1000;
- box-shadow: #AAAAAA .2vw .2vw .4vw;
+ box-shadow: #aaaaaa 0.2vw 0.2vw 0.4vw;
flex-direction: column;
- border: 1px solid #BBBBBBBB;
+ border: 1px solid #bbbbbbbb;
border-radius: 15px;
padding-top: 10px;
padding-bottom: 10px;
@@ -49,7 +49,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
+ transition: all 0.1s;
border-style: none;
// padding: 10px 0px 10px 0px;
white-space: nowrap;
@@ -58,7 +58,7 @@
text-transform: uppercase;
padding-right: 30px;
- .icon-background {
+ .contextMenu-item-icon-background {
pointer-events: all;
background-color: transparent;
width: 35px;
@@ -78,7 +78,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
+ transition: all 0.1s;
border-style: none;
// padding: 10px 0px 10px 0px;
white-space: nowrap;
@@ -89,7 +89,7 @@
}
.contextMenu-item:hover {
- border-width: .11px;
+ border-width: 0.11px;
border-style: none;
border-color: $medium-gray; // rgb(187, 186, 186);
border-bottom-style: solid;
@@ -116,8 +116,8 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- transition: all .1s;
- border-width: .11px;
+ transition: all 0.1s;
+ border-width: 0.11px;
border-style: none;
border-color: $medium-gray; // rgb(187, 186, 186);
// padding: 10px 0px 10px 0px;
@@ -152,4 +152,4 @@
padding-left: 10px;
border: solid black 1px;
border-radius: 5px;
-} \ No newline at end of file
+}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 80ff16cf9..c9908b4b0 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,10 +1,10 @@
-import React = require("react");
-import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem";
-import { observable, action, computed, runInAction, IReactionDisposer, reaction } from "mobx";
-import { observer } from "mobx-react";
-import "./ContextMenu.scss";
+import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import Measure from "react-measure";
+import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import './ContextMenu.scss';
+import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
+import { Utils } from '../../Utils';
@observer
export class ContextMenu extends React.Component {
@@ -14,7 +14,7 @@ export class ContextMenu extends React.Component {
@observable private _pageX: number = 0;
@observable private _pageY: number = 0;
@observable private _display: boolean = false;
- @observable private _searchString: string = "";
+ @observable private _searchString: string = '';
@observable private _showSearch: boolean = false;
// afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
@observable private _yRelativeToTop: boolean = true;
@@ -43,9 +43,10 @@ export class ContextMenu extends React.Component {
onPointerDown = (e: PointerEvent) => {
this._mouseX = e.clientX;
this._mouseY = e.clientY;
- }
+ };
@action
onPointerUp = (e: PointerEvent) => {
+ if (e.button !== 2 && !e.ctrlKey) return;
const curX = e.clientX;
const curY = e.clientY;
if (this._ignoreUp) {
@@ -63,28 +64,27 @@ export class ContextMenu extends React.Component {
this._display = true;
}
}
- }
+ };
componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onPointerDown);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointerdown', this.onPointerDown, true);
+ document.removeEventListener('pointerup', this.onPointerUp);
this._reactionDisposer?.();
}
@action
componentDidMount = () => {
- document.addEventListener("pointerdown", this.onPointerDown);
- document.addEventListener("pointerup", this.onPointerUp);
-
- }
+ document.addEventListener('pointerdown', this.onPointerDown, true);
+ document.addEventListener('pointerup', this.onPointerUp);
+ };
@action
clearItems() {
this._items = [];
- this._defaultPrefix = "";
+ this._defaultPrefix = '';
this._defaultItem = undefined;
}
- _defaultPrefix: string = "";
+ _defaultPrefix: string = '';
_defaultItem: ((name: string) => void) | undefined;
findByDescription = (target: string, toLowerCase = false) => {
@@ -93,7 +93,7 @@ export class ContextMenu extends React.Component {
toLowerCase && (reference = reference.toLowerCase());
return reference === target;
});
- }
+ };
@action
addItem(item: ContextMenuProps) {
@@ -104,9 +104,9 @@ export class ContextMenu extends React.Component {
@action
moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
if (after && this.findByDescription(after.description)) {
- const curInd = this._items.findIndex((i) => i.description === item.description);
+ const curInd = this._items.findIndex(i => i.description === item.description);
this._items.splice(curInd, 1);
- const afterInd = this._items.findIndex((i) => i.description === after.description);
+ const afterInd = this._items.findIndex(i => i.description === after.description);
this._items.splice(afterInd + 1, 0, item);
}
}
@@ -116,10 +116,6 @@ export class ContextMenu extends React.Component {
this._defaultItem = item;
}
- getItems() {
- return this._items;
- }
-
static readonly buffer = 20;
get pageX() {
const x = this._pageX;
@@ -147,7 +143,7 @@ export class ContextMenu extends React.Component {
_onDisplay?: () => void = undefined;
@action
- displayMenu = (x: number, y: number, initSearch = "", showSearch = false, onDisplay?: () => void) => {
+ displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
//maxX and maxY will change if the UI/font size changes, but will work for any amount
//of items added to the menu
@@ -158,7 +154,7 @@ export class ContextMenu extends React.Component {
this._shouldDisplay = true;
this._onDisplay = onDisplay;
this._display = !onDisplay;
- }
+ };
@action
closeMenu = () => {
@@ -167,10 +163,10 @@ export class ContextMenu extends React.Component {
this._display = false;
this._shouldDisplay = false;
return wasOpen;
- }
+ };
@computed get filteredItems(): (OriginalMenuProps | string[])[] {
- const searchString = this._searchString.toLowerCase().split(" ");
+ const searchString = this._searchString.toLowerCase().split(' ');
const matches = (descriptions: string[]): boolean => {
return searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
};
@@ -181,7 +177,7 @@ export class ContextMenu extends React.Component {
for (const item of items) {
const description = item.description;
const path = groupFunc(description);
- if ("subitems" in item) {
+ if ('subitems' in item) {
const children = flattenItems(item.subitems, name => [...groupFunc(description), name]);
if (children.length || matches(path)) {
eles.push(path);
@@ -199,37 +195,26 @@ export class ContextMenu extends React.Component {
return eles;
};
- return flattenItems(this._items, name => [name]);
+ return flattenItems(this._items.slice(), name => [name]);
}
@computed get flatItems(): OriginalMenuProps[] {
return this.filteredItems.filter(item => !Array.isArray(item)) as OriginalMenuProps[];
}
- @computed get filteredViews() {
- const createGroupHeader = (contents: any) => {
- return (
- <div className="contextMenu-group">
- <div className="contextMenu-description">{contents}</div>
- </div>
- );
- };
- const createItem = (item: ContextMenuProps, selected: boolean) => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} selected={selected} />;
- let itemIndex = 0;
- return this.filteredItems.map(value => {
- if (Array.isArray(value)) {
- return createGroupHeader(value.join(" -> "));
- } else {
- return createItem(value, itemIndex++ === this.selectedIndex);
- }
- });
- }
-
@computed get menuItems() {
if (!this._searchString) {
return this._items.map((item, ind) => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={ind + item.description} closeMenu={this.closeMenu} />);
}
- return this.filteredViews;
+ return this.filteredItems.map((value, index) =>
+ Array.isArray(value) ? (
+ <div className="contextMenu-group">
+ <div className="contextMenu-description">{value.join(' -> ')}</div>
+ </div>
+ ) : (
+ <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this.selectedIndex} />
+ )
+ );
}
@computed get itemsNeedSearch() {
@@ -237,48 +222,34 @@ export class ContextMenu extends React.Component {
}
render() {
- if (!this._display) {
- return null;
- }
- const style = this._yRelativeToTop ? { left: this.pageX, top: this.pageY } :
- { left: this.pageX, bottom: this.pageY };
-
- const contents = (
- <>
- {!this.itemsNeedSearch ? (null) :
- <span className={"search-icon"}>
+ return !this._display ? null : (
+ <div className="contextMenu-cont" style={{ left: this.pageX, ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }) }}>
+ {!this.itemsNeedSearch ? null : (
+ <span className={'search-icon'}>
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
<input className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
- </span>}
- {this.menuItems}
- </>
- );
- return (
- <Measure offset onResize={action((r: any) => { this._width = r.offset.width; this._height = r.offset.height; })}>
- {({ measureRef }) => (
- <div className="contextMenu-cont" style={style} ref={measureRef}>
- {contents}
- </div>
+ </span>
)}
- </Measure>
+ {this.menuItems}
+ </div>
);
}
@action
onKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === "ArrowDown") {
+ if (e.key === 'ArrowDown') {
if (this.selectedIndex < this.flatItems.length - 1) {
this.selectedIndex++;
}
e.preventDefault();
- } else if (e.key === "ArrowUp") {
+ } else if (e.key === 'ArrowUp') {
if (this.selectedIndex > 0) {
this.selectedIndex--;
}
e.preventDefault();
- } else if (e.key === "Enter" || e.key === "Tab") {
+ } else if (e.key === 'Enter' || e.key === 'Tab') {
const item = this.flatItems[this.selectedIndex];
if (item) {
item.event({ x: this.pageX, y: this.pageY });
@@ -287,21 +258,21 @@ export class ContextMenu extends React.Component {
}
this.closeMenu();
e.preventDefault();
+ e.stopPropagation();
}
- }
+ };
@action
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this._searchString = e.target.value;
if (!this._searchString) {
this.selectedIndex = -1;
- }
- else {
+ } else {
if (this.selectedIndex === -1) {
this.selectedIndex = 0;
} else {
this.selectedIndex = Math.min(this.flatItems.length - 1, this.selectedIndex);
}
}
- }
-} \ No newline at end of file
+ };
+}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index c3921d846..dc9c2eb6c 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,9 +1,9 @@
-import React = require("react");
-import { observable, action } from "mobx";
-import { observer } from "mobx-react";
+import React = require('react');
+import { observable, action, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager } from '../util/UndoManager';
export interface OriginalMenuProps {
description: string;
@@ -28,18 +28,17 @@ export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
export class ContextMenuItem extends React.Component<ContextMenuProps & { selected?: boolean }> {
@observable private _items: Array<ContextMenuProps> = [];
@observable private overItem = false;
- @observable private subRef = React.createRef<HTMLDivElement>();
- constructor(props: ContextMenuProps | SubmenuProps) {
- super(props);
- if ((this.props as SubmenuProps).subitems) {
- (this.props as SubmenuProps).subitems?.forEach(i => this._items.push(i));
+ componentDidMount() {
+ this._items.length = 0;
+ if ((this.props as SubmenuProps)?.subitems) {
+ (this.props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
}
}
handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
- if ("event" in this.props) {
- this.props.closeMenu && this.props.closeMenu();
+ if ('event' in this.props) {
+ this.props.closeMenu?.();
let batch: UndoManager.Batch | undefined;
if (this.props.undoable !== false) {
batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`);
@@ -47,7 +46,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
await this.props.event({ x: e.clientX, y: e.clientY });
batch?.end();
}
- }
+ };
currentTimeout?: any;
static readonly timeout = 300;
@@ -63,8 +62,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
}
this._overPosY = e.clientY;
this._overPosX = e.clientX;
- this.currentTimeout = setTimeout(action(() => this.overItem = true), ContextMenuItem.timeout);
- }
+ this.currentTimeout = setTimeout(
+ action(() => (this.overItem = true)),
+ ContextMenuItem.timeout
+ );
+ };
onPointerLeave = () => {
if (this.currentTimeout) {
@@ -74,60 +76,68 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
if (!this.overItem) {
return;
}
- this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout);
- }
+ this.currentTimeout = setTimeout(
+ action(() => (this.overItem = false)),
+ ContextMenuItem.timeout
+ );
+ };
render() {
-
-
-
- if ("event" in this.props) {
+ if ('event' in this.props) {
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onPointerDown={this.handleEvent}>
+ <div className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
{this.props.icon ? (
- <span className="icon-background">
+ <span className="contextMenu-item-icon-background">
<FontAwesomeIcon icon={this.props.icon} size="sm" />
</span>
) : null}
- <div className="contextMenu-description">
- {this.props.description.replace(":","")}
- </div>
+ <div className="contextMenu-description">{this.props.description.replace(':', '')}</div>
</div>
);
- } else if ("subitems" in this.props) {
- const where = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "flex-start" : this._overPosY > window.innerHeight * 2 / 3 ? "flex-end" : "center";
- const marginTop = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "20px" : this._overPosY > window.innerHeight * 2 / 3 ? "-20px" : "";
+ } else if ('subitems' in this.props) {
+ const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
+ const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
// here
- const submenu = !this.overItem ? (null) :
- <div className="contextMenu-subMenu-cont"
+ const submenu = !this.overItem ? null : (
+ <div
+ className="contextMenu-subMenu-cont"
style={{
- marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? "90%" : "20%", marginTop
+ marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%',
+ marginTop,
}}>
- {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
- </div>;
+ {this._items.map(prop => (
+ <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+ ))}
+ </div>
+ );
if (!(this.props as SubmenuProps).noexpand) {
- return <div className="contextMenu-inlineMenu">
- {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
- </div>;
+ return (
+ <div className="contextMenu-inlineMenu">
+ {this._items.map(prop => (
+ <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+ ))}
+ </div>
+ );
}
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")}
- style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
- onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
+ <div
+ className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')}
+ style={{ alignItems: where, borderTop: this.props.addDivider ? 'solid 1px' : undefined }}
+ onMouseLeave={this.onPointerLeave}
+ onMouseEnter={this.onPointerEnter}>
{this.props.icon ? (
- <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center", alignSelf: "center" }}>
+ <span className="contextMenu-item-icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: 'center', alignSelf: 'center' }}>
<FontAwesomeIcon icon={this.props.icon} size="sm" />
</span>
) : null}
- <div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
- style={{ alignItems: "center", alignSelf: "center" }} >
+ <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
{this.props.description}
- <FontAwesomeIcon icon={"angle-right"} size="lg" style={{ position: "absolute", right: "10px" }} />
+ <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
</div>
{submenu}
</div>
);
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss
new file mode 100644
index 000000000..3db23b86f
--- /dev/null
+++ b/src/client/views/DashboardView.scss
@@ -0,0 +1,66 @@
+.dashboard-view {
+ padding: 50px;
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ position: absolute;
+
+ .left-menu {
+ display: flex;
+ flex-direction: column;
+ width: 300px;
+ }
+
+ .all-dashboards {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ overflow-y: scroll;
+ }
+}
+
+.text-button {
+ padding: 10px 0;
+ &:hover {
+ font-weight: 500;
+ }
+
+ &.selected {
+ font-weight: 700;
+ }
+}
+
+.dashboard-container {
+ border-radius: 10px;
+ width: 250px;
+ border: solid .5px grey;
+ display: flex;
+ flex-direction: column;
+ margin: 0 30px 30px 30px;
+ overflow: hidden;
+
+ &:hover {
+ border: solid 1.5px grey;
+ }
+
+ .title {
+ margin: 10px;
+ font-weight: 500;
+ }
+
+ img {
+ width: auto;
+ height: 80%;
+ }
+
+ .info {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .more {
+ z-index: 100;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
new file mode 100644
index 000000000..84b1017b4
--- /dev/null
+++ b/src/client/views/DashboardView.tsx
@@ -0,0 +1,306 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { DataSym, Doc, DocListCast, DocListCastAsync } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { List } from '../../fields/List';
+import { Cast, ImageCast, StrCast } from '../../fields/Types';
+import { DocServer } from '../DocServer';
+import { Docs, DocumentOptions } from '../documents/Documents';
+import { CollectionViewType } from '../documents/DocumentTypes';
+import { HistoryUtil } from '../util/History';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SharingManager } from '../util/SharingManager';
+import { undoBatch } from '../util/UndoManager';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionView } from './collections/CollectionView';
+import { ContextMenu } from './ContextMenu';
+import './DashboardView.scss';
+import { MainViewModal } from './MainViewModal';
+
+enum DashboardGroup {
+ MyDashboards,
+ SharedDashboards,
+}
+
+// DashboardView is the view with the dashboard previews, rendered when the app first loads
+
+@observer
+export class DashboardView extends React.Component {
+ //TODO: delete dashboard, share dashboard, etc.
+
+ public static _urlState: HistoryUtil.DocUrl;
+
+ @observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
+
+ @observable private newDashboardName: string | undefined = undefined;
+ @action abortCreateNewDashboard = () => {
+ this.newDashboardName = undefined;
+ };
+ @action setNewDashboardName(name: string) {
+ this.newDashboardName = name;
+ }
+
+ @action
+ selectDashboardGroup = (group: DashboardGroup) => {
+ this.selectedDashboardGroup = group;
+ };
+
+ clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => {
+ if (e.detail === 2) {
+ Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
+ Doc.ActiveDashboard = dashboard;
+ Doc.ActivePage = 'dashboard';
+ }
+ };
+
+ getDashboards = () => {
+ const allDashboards = DocListCast(Doc.MyDashboards.data);
+ if (this.selectedDashboardGroup === DashboardGroup.MyDashboards) {
+ return allDashboards.filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail);
+ } else {
+ const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking);
+ return sharedDashboards;
+ }
+ };
+
+ isUnviewedSharedDashboard = (dashboard: Doc): boolean => {
+ // const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking);
+ return !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard);
+ };
+
+ getSharedDashboards = () => {
+ const sharedDashs = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking);
+ return sharedDashs.filter(dashboard => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard));
+ };
+
+ @undoBatch
+ createNewDashboard = async (name: string) => {
+ DashboardView.createNewDashboard(undefined, name);
+ this.abortCreateNewDashboard();
+ };
+
+ @computed
+ get namingInterface() {
+ return (
+ <div>
+ <input className="password-inputs" placeholder="Untitled Dashboard" onChange={e => this.setNewDashboardName((e.target as any).value)} />
+ <button className="password-submit" onClick={this.abortCreateNewDashboard}>
+ Cancel
+ </button>
+ <button
+ className="password-submit"
+ onClick={() => {
+ this.createNewDashboard(this.newDashboardName!);
+ }}>
+ Create
+ </button>
+ </div>
+ );
+ }
+
+ _downX: number = 0;
+ _downY: number = 0;
+ @action
+ onContextMenu = (dashboard: Doc, e?: React.MouseEvent, pageX?: number, pageY?: number) => {
+ // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
+ if (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.persist();
+
+ if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
+ return;
+ }
+ const cm = ContextMenu.Instance;
+ cm.addItem({
+ description: 'Share Dashboard',
+ event: async () => {
+ SharingManager.Instance.open(undefined, dashboard);
+ },
+ icon: 'edit',
+ });
+ cm.addItem({
+ description: 'Delete Dashboard',
+ event: async () => {
+ DashboardView.removeDashboard(dashboard);
+ },
+ icon: 'trash',
+ });
+ cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
+ }
+ };
+
+ render() {
+ return (
+ <>
+ <div className="dashboard-view">
+ <div className="left-menu">
+ <div
+ className="text-button"
+ onClick={() => {
+ this.setNewDashboardName('');
+ }}>
+ New
+ </div>
+ <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.MyDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)}>
+ My Dashboards
+ </div>
+ <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.SharedDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.SharedDashboards)}>
+ Shared Dashboards
+ </div>
+ </div>
+ <div className="all-dashboards">
+ {this.getDashboards().map(dashboard => {
+ const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href;
+ return (
+ <div
+ className="dashboard-container"
+ key={dashboard[Id]}
+ onContextMenu={e => {
+ this.onContextMenu(dashboard, e);
+ }}
+ onClick={e => this.clickDashboard(e, dashboard)}>
+ <img
+ src={
+ href ?? 'https://media.istockphoto.com/photos/hot-air-balloons-flying-over-the-botan-canyon-in-turkey-picture-id1297349747?b=1&k=20&m=1297349747&s=170667a&w=0&h=oH31fJty_4xWl_JQ4OIQWZKP8C6ji9Mz7L4XmEnbqRU='
+ }></img>
+ <div className="info">
+ <div className="title"> {StrCast(dashboard.title)} </div>
+ {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>}
+ <div
+ className="more"
+ onPointerDown={e => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ }}
+ onClick={e => {
+ this.onContextMenu(dashboard, e);
+ }}>
+ <FontAwesomeIcon color="black" size="lg" icon="bars" />
+ </div>
+ </div>
+ </div>
+ );
+ })}
+ </div>
+ </div>
+ <MainViewModal
+ contents={this.namingInterface}
+ isDisplayed={this.newDashboardName !== undefined}
+ interactive={true}
+ closeOnExternalClick={this.abortCreateNewDashboard}
+ dialogueBoxStyle={{ width: '500px', height: '300px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }}
+ />
+ </>
+ );
+ }
+
+ public static closeActiveDashboard() {
+ Doc.ActiveDashboard = undefined;
+ }
+ public static snapshotDashboard() {
+ return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
+ }
+
+ /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there).
+ /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url)
+ public static openDashboard = (doc: Doc | undefined, fromHistory = false) => {
+ if (!doc) return false;
+ Doc.MainDocId = doc[Id];
+ Doc.AddDocToList(Doc.MyDashboards, 'data', doc);
+
+ // this has the side-effect of setting the main container since we're assigning the active/guest dashboard
+ Doc.UserDoc() ? (Doc.ActiveDashboard = doc) : (Doc.GuestDashboard = doc);
+
+ const state = DashboardView._urlState;
+ if (state.sharing === true && !Doc.UserDoc()) {
+ DocServer.Control.makeReadOnly();
+ } else {
+ fromHistory ||
+ HistoryUtil.pushState({
+ type: 'doc',
+ docId: doc[Id],
+ readonly: state.readonly,
+ nro: state.nro,
+ sharing: false,
+ });
+ if (state.readonly === true || state.readonly === null) {
+ DocServer.Control.makeReadOnly();
+ } else if (state.safe) {
+ if (!state.nro) {
+ DocServer.Control.makeReadOnly();
+ }
+ CollectionView.SetSafeMode(true);
+ } else if (state.nro || state.nro === null || state.readonly === false) {
+ } else if (doc.readOnly) {
+ DocServer.Control.makeReadOnly();
+ } else {
+ Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
+ }
+ }
+
+ return true;
+ };
+
+ public static removeDashboard = async (dashboard: Doc) => {
+ const dashboards = await DocListCastAsync(Doc.MyDashboards.data);
+ if (dashboards?.length) {
+ if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.find(doc => doc !== dashboard));
+ Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard);
+ if (!dashboards.length) Doc.ActivePage = 'home';
+ }
+ };
+
+ public static createNewDashboard = (id?: string, name?: string) => {
+ const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true);
+ const dashboards = Doc.MyDashboards;
+ const dashboardCount = DocListCast(dashboards.data).length + 1;
+ const freeformOptions: DocumentOptions = {
+ x: 0,
+ y: 400,
+ _width: 1500,
+ _height: 1000,
+ _fitWidth: true,
+ _backgroundGridShow: true,
+ title: `Untitled Tab 1`,
+ };
+ const title = name ? name : `Dashboard ${dashboardCount}`;
+ const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
+ const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
+ freeformDoc.context = dashboardDoc;
+
+ // switching the tabs from the datadoc to the regular doc
+ const dashboardTabs = DocListCast(dashboardDoc[DataSym].data);
+ dashboardDoc.data = new List<Doc>(dashboardTabs);
+ dashboardDoc['pane-count'] = 1;
+
+ Doc.ActivePresentation = presentation;
+
+ Doc.AddDocToList(dashboards, 'data', dashboardDoc);
+ // open this new dashboard
+ Doc.ActiveDashboard = dashboardDoc;
+ Doc.ActivePage = 'dashboard';
+ };
+}
+
+export function AddToList(MySharedDocs: Doc, arg1: string, dash: any) {
+ throw new Error('Function not implemented.');
+}
+
+ScriptingGlobals.add(function createNewDashboard() {
+ return DashboardView.createNewDashboard();
+}, 'creates a new dashboard when called');
+ScriptingGlobals.add(function shareDashboard(dashboard: Doc) {
+ SharingManager.Instance.open(undefined, dashboard);
+}, 'opens sharing dialog for Dashboard');
+ScriptingGlobals.add(function removeDashboard(dashboard: Doc) {
+ DashboardView.removeDashboard(dashboard);
+}, 'Remove Dashboard from Dashboards');
+ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
+ DashboardView.openDashboard(Doc.MakeAlias(dashboard));
+}, 'adds Dashboard to set of Dashboards');
+ScriptingGlobals.add(function snapshotDashboard() {
+ DashboardView.snapshotDashboard();
+}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 2e6ea1faa..280ca8a8c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -8,12 +8,11 @@ import { Cast, ScriptCast } from '../../fields/Types';
import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util';
import { returnFalse } from '../../Utils';
import { DocUtils } from '../documents/Documents';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { InteractionUtils } from '../util/InteractionUtils';
import { UndoManager } from '../util/UndoManager';
+import { DocumentView } from './nodes/DocumentView';
import { Touchable } from './Touchable';
-
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
export interface DocComponentProps {
Document: Doc;
@@ -23,13 +22,21 @@ export interface DocComponentProps {
export function DocComponent<P extends DocComponentProps>() {
class Component extends Touchable<P> {
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
- @computed get Document() { return this.props.Document; }
+ @computed get Document() {
+ return this.props.Document;
+ }
// This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ @computed get rootDoc() {
+ return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document;
+ }
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() { return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
+ @computed get layoutDoc() {
+ return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.());
+ }
// This is the data part of a document -- ie, the data that is constant across all views of the document
- @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
+ @computed get dataDoc() {
+ return this.props.Document[DataSym] as Doc;
+ }
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
@@ -41,8 +48,8 @@ interface ViewBoxBaseProps {
Document: Doc;
DataDoc?: Doc;
ContainingCollectionDoc: Opt<Doc>;
+ DocumentView?: () => DocumentView;
fieldKey: string;
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
isSelected: (outsideReaction?: boolean) => boolean;
isContentActive: () => boolean | undefined;
renderDepth: number;
@@ -54,36 +61,39 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
//@computed get Document(): T { return schemaCtor(this.props.Document); }
// This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ @computed get rootDoc() {
+ return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document;
+ }
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+ @computed get layoutDoc() {
+ return Doc.Layout(this.props.Document);
+ }
// This is the data part of a document -- ie, the data that is constant across all views of the document
- @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
-
+ @computed get dataDoc() {
+ return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym];
+ }
// key where data is stored
- @computed get fieldKey() { return this.props.fieldKey; }
-
- lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
+ @computed get fieldKey() {
+ return this.props.fieldKey;
+ }
- isContentActive = (outsideReaction?: boolean) => (
- this.props.isContentActive?.() === false ? false :
- (CurrentUserUtils.SelectedTool !== InkTool.None ||
- (this.props.isContentActive?.() || this.props.Document.forceActive ||
- this.props.isSelected(outsideReaction) ||
- this.props.rootSelected(outsideReaction)) ? true : undefined))
+ isContentActive = (outsideReaction?: boolean) =>
+ this.props.isContentActive?.() === false
+ ? false
+ : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction)
+ ? true
+ : undefined;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
}
-
/// DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
export interface ViewBoxAnnotatableProps {
Document: Doc;
DataDoc?: Doc;
fieldKey: string;
- filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
+ filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
isContentActive: () => boolean | undefined;
select: (isCtrlPressed: boolean) => void;
whenChildContentsActiveChanged: (isActive: boolean) => void;
@@ -94,20 +104,29 @@ export interface ViewBoxAnnotatableProps {
}
export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
class Component extends Touchable<P> {
- @observable _annotationKeySuffix = () => "annotations";
-
+ @observable _annotationKeySuffix = () => 'annotations';
@observable _isAnyChildContentActive = false;
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
- @computed get Document() { return this.props.Document; }
+ @computed get Document() {
+ return this.props.Document;
+ }
// This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ @computed get rootDoc() {
+ return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document;
+ }
// This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+ @computed get layoutDoc() {
+ return Doc.Layout(this.props.Document);
+ }
// This is the data part of a document -- ie, the data that is constant across all views of the document
- @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
+ @computed get dataDoc() {
+ return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym];
+ }
// key where data is stored
- @computed get fieldKey() { return this.props.fieldKey; }
+ @computed get fieldKey() {
+ return this.props.fieldKey;
+ }
isAnyChildContentActive = () => this._isAnyChildContentActive;
@@ -115,20 +134,23 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
styleFromLayoutString = (scale: number) => {
const style: { [key: string]: any } = {};
- const divKeys = ["width", "height", "fontSize", "transform", "left", "background", "left", "right", "top", "bottom", "pointerEvents", "position"];
- const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a property expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? "";
+ const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'background', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
+ const replacer = (match: any, expr: string, offset: any, string: any) => {
+ // bcz: this executes a script to convert a property expression string: { script } into a value
+ return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? '';
};
divKeys.map((prop: string) => {
const p = (this.props as any)[prop];
- typeof p === "string" && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+ typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
});
return style;
- }
+ };
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- @computed public get annotationKey() { return this.fieldKey + (this._annotationKeySuffix() ? "-" + this._annotationKeySuffix() : ""); }
+ @computed public get annotationKey() {
+ return this.fieldKey + (this._annotationKeySuffix() ? '-' + this._annotationKeySuffix() : '');
+ }
@action.bound
removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean {
@@ -136,26 +158,29 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
const indocs = doc instanceof Doc ? [doc] : doc;
const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
if (docs.length) {
- setTimeout(() => docs.map(doc => { // this allows 'addDocument' to see the annotationOn field in order to create a pushin
- Doc.SetInPlace(doc, "isPushpin", undefined, true);
- doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, "annotationOn", undefined, true);
- }));
+ setTimeout(() =>
+ docs.map(doc => {
+ // this allows 'addDocument' to see the annotationOn field in order to create a pushin
+ Doc.SetInPlace(doc, 'isPushpin', undefined, true);
+ doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true);
+ })
+ );
const targetDataDoc = this.dataDoc;
const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
const toRemove = value.filter(v => docs.includes(v));
if (toRemove.length !== 0) {
- const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
+ const recent = Doc.MyRecentlyClosed;
toRemove.forEach(doc => {
leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
doc.context = undefined;
if (recent) {
- Doc.RemoveDocFromList(recent, "data", doc);
- Doc.AddDocToList(recent, "data", doc, undefined, true, true);
+ Doc.RemoveDocFromList(recent, 'data', doc);
+ Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
}
});
- this.props.select(false);
+ this.isAnyChildContentActive() && this.props.select(false);
return true;
}
}
@@ -176,24 +201,22 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey));
}
return false;
- }
+ };
@action.bound
addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
const docs = doc instanceof Doc ? [doc] : doc;
- if (this.props.filterAddDocument?.(docs) === false ||
- docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
+ if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
return false;
}
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
const added = docs.filter(d => !docList.includes(d));
- const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+ const effectiveAcl = GetEffectiveAcl(targetDataDoc);
if (added.length) {
if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
return false;
- }
- else {
+ } else {
if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
added.forEach(d => {
for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
@@ -204,34 +227,34 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
if (effectiveAcl === AclAugment) {
added.map(doc => {
- if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
+ if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
doc.context = this.props.Document;
if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
- this.props.layerProvider?.(doc, true);
Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
});
- }
- else {
- added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document
- this.props.layerProvider?.(doc, true);
- //DocUtils.LeavePushpin(doc);
- doc._stayInCollection = undefined;
- doc.context = this.props.Document;
- if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
+ } else {
+ added
+ .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)))
+ .map(doc => {
+ // only make a pushpin if we have acl's to edit the document
+ //DocUtils.LeavePushpin(doc);
+ doc._stayInCollection = undefined;
+ doc.context = this.props.Document;
+ if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
- inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
- });
+ Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
+ });
const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
if (annoDocs instanceof List) annoDocs.push(...added);
else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
- targetDataDoc[(annotationKey ?? this.annotationKey) + "-lastModified"] = new DateField(new Date(Date.now()));
+ targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now()));
}
}
}
return true;
- }
+ };
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
+ whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
}
return Component;
-} \ No newline at end of file
+}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 2293da6c8..1d4056759 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,57 +1,54 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocCastAsync } from "../../fields/Doc";
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../fields/Doc';
import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast, StrCast } from "../../fields/Types";
-import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from "../../Utils";
+import { Cast, NumCast } from '../../fields/Types';
+import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
import { Docs } from '../documents/Documents';
-import { DocumentType } from '../documents/DocumentTypes';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { DragManager } from '../util/DragManager';
import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
+import { undoBatch } from '../util/UndoManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { TabDocView } from './collections/TabDocView';
import './DocumentButtonBar.scss';
+import { Colors } from './global/globalEnums';
import { MetadataEntryMenu } from './MetadataEntryMenu';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
-import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
-import { TemplateMenu } from "./TemplateMenu";
-import React = require("react");
-import { PresBox } from './nodes/trails/PresBox';
-import { undoBatch } from '../util/UndoManager';
-import { CollectionViewType } from './collections/CollectionView';
-import { Colors } from './global/globalEnums';
import { DashFieldView } from './nodes/formattedText/DashFieldView';
-const higflyout = require("@hig/flyout");
+import { GoogleRef } from './nodes/formattedText/FormattedTextBox';
+import { TemplateMenu } from './TemplateMenu';
+import React = require('react');
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-const cloud: IconProp = "cloud-upload-alt";
-const fetch: IconProp = "sync-alt";
+const cloud: IconProp = 'cloud-upload-alt';
+const fetch: IconProp = 'sync-alt';
enum UtilityButtonState {
Default,
OpenRight,
- OpenExternally
+ OpenExternally,
}
@observer
-export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[], stack?: any }, {}> {
+export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[]; stack?: any }, {}> {
private _dragRef = React.createRef<HTMLDivElement>();
private _pullAnimating = false;
private _pushAnimating = false;
private _pullColorAnimating = false;
- @observable private pushIcon: IconProp = "arrow-alt-circle-up";
- @observable private pullIcon: IconProp = "arrow-alt-circle-down";
- @observable private pullColor: string = "white";
+ @observable private pushIcon: IconProp = 'arrow-alt-circle-up';
+ @observable private pullIcon: IconProp = 'arrow-alt-circle-down';
+ @observable private pullColor: string = 'white';
@observable public isAnimatingFetch = false;
@observable public isAnimatingPulse = false;
@@ -63,17 +60,21 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
constructor(props: { views: () => (DocumentView | undefined)[] }) {
super(props);
- runInAction(() => DocumentButtonBar.Instance = this);
+ runInAction(() => (DocumentButtonBar.Instance = this));
}
public startPullOutcome = action((success: boolean) => {
if (!this._pullAnimating) {
this._pullAnimating = true;
- this.pullIcon = success ? "check-circle" : "stop-circle";
- setTimeout(() => runInAction(() => {
- this.pullIcon = "arrow-alt-circle-down";
- this._pullAnimating = false;
- }), 1000);
+ this.pullIcon = success ? 'check-circle' : 'stop-circle';
+ setTimeout(
+ () =>
+ runInAction(() => {
+ this.pullIcon = 'arrow-alt-circle-down';
+ this._pullAnimating = false;
+ }),
+ 1000
+ );
}
});
@@ -81,11 +82,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
this.isAnimatingPulse = false;
if (!this._pushAnimating) {
this._pushAnimating = true;
- this.pushIcon = success ? "check-circle" : "stop-circle";
- setTimeout(() => runInAction(() => {
- this.pushIcon = "arrow-alt-circle-up";
- this._pushAnimating = false;
- }), 1000);
+ this.pushIcon = success ? 'check-circle' : 'stop-circle';
+ setTimeout(
+ () =>
+ runInAction(() => {
+ this.pushIcon = 'arrow-alt-circle-up';
+ this._pushAnimating = false;
+ }),
+ 1000
+ );
}
});
@@ -93,209 +98,244 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
this.isAnimatingFetch = false;
if (!this._pullColorAnimating) {
this._pullColorAnimating = true;
- this.pullColor = unchanged ? "lawngreen" : "red";
+ this.pullColor = unchanged ? 'lawngreen' : 'red';
setTimeout(this.clearPullColor, 1000);
}
});
private clearPullColor = action(() => {
- this.pullColor = "white";
+ this.pullColor = 'white';
this._pullColorAnimating = false;
});
- get view0() { return this.props.views()?.[0]; }
+ get view0() {
+ return this.props.views()?.[0];
+ }
@computed
get considerGoogleDocsPush() {
const targetDoc = this.view0?.props.Document;
const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
- const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}>
- <div
- className="documentButtonBar-button"
- style={{ animation }}
- onClick={async () => {
- await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- !published && runInAction(() => this.isAnimatingPulse = true);
- DocumentButtonBar.hasPushedHack = false;
- targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
- </div></Tooltip>;
+ const animation = this.isAnimatingPulse ? 'shadow-pulse 1s linear infinite' : 'none';
+ return !targetDoc ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{`${published ? 'Push' : 'Publish'} to Google Docs`}</div>
+ </>
+ }>
+ <div
+ className="documentButtonBar-button"
+ style={{ animation }}
+ onClick={async () => {
+ await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
+ !published && runInAction(() => (this.isAnimatingPulse = true));
+ DocumentButtonBar.hasPushedHack = false;
+ targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? 'sm' : 'xs'} />
+ </div>
+ </Tooltip>
+ );
}
@computed
get considerGoogleDocsPull() {
const targetDoc = this.view0?.props.Document;
const dataDoc = targetDoc && Doc.GetProto(targetDoc);
- const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
+ const animation = this.isAnimatingFetch ? 'spin 0.5s linear infinite' : 'none';
const title = (() => {
switch (this.openHover) {
default:
- case UtilityButtonState.Default: return `${!dataDoc?.googleDocUnchanged ? "Pull from" : "Fetch"} Google Docs`;
- case UtilityButtonState.OpenRight: return "Open in Right Split";
- case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
+ case UtilityButtonState.Default:
+ return `${!dataDoc?.googleDocUnchanged ? 'Pull from' : 'Fetch'} Google Docs`;
+ case UtilityButtonState.OpenRight:
+ return 'Open in Right Split';
+ case UtilityButtonState.OpenExternally:
+ return 'Open in new Browser Tab';
}
})();
- return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{title}</div></>}>
- <div className="documentButtonBar-button"
- style={{ backgroundColor: this.pullColor }}
- onPointerEnter={action(e => {
- if (e.altKey) {
- this.openHover = UtilityButtonState.OpenExternally;
- } else if (e.shiftKey) {
- this.openHover = UtilityButtonState.OpenRight;
- }
- })}
- onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
- onClick={async e => {
- const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
- if (e.shiftKey) {
- e.preventDefault();
- let googleDoc = await Cast(dataDoc.googleDoc, Doc);
- if (!googleDoc) {
- const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false };
- googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
- dataDoc.googleDoc = googleDoc;
+ return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{title}</div>
+ </>
+ }>
+ <div
+ className="documentButtonBar-button"
+ style={{ backgroundColor: this.pullColor }}
+ onPointerEnter={action(e => {
+ if (e.altKey) {
+ this.openHover = UtilityButtonState.OpenExternally;
+ } else if (e.shiftKey) {
+ this.openHover = UtilityButtonState.OpenRight;
}
- CollectionDockingView.AddSplit(googleDoc, "right");
- } else if (e.altKey) {
- e.preventDefault();
- window.open(googleDocUrl);
- } else {
- this.clearPullColor();
- DocumentButtonBar.hasPulledHack = false;
- targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
- dataDoc.googleDocUnchanged && runInAction(() => this.isAnimatingFetch = true);
- }
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm"
- style={{ WebkitAnimation: animation, MozAnimation: animation }}
- icon={(() => {
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch;
- case UtilityButtonState.OpenRight: return "arrow-alt-circle-right";
- case UtilityButtonState.OpenExternally: return "share";
+ })}
+ onPointerLeave={action(() => (this.openHover = UtilityButtonState.Default))}
+ onClick={async e => {
+ const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
+ if (e.shiftKey) {
+ e.preventDefault();
+ let googleDoc = await Cast(dataDoc.googleDoc, Doc);
+ if (!googleDoc) {
+ const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false };
+ googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
+ dataDoc.googleDoc = googleDoc;
+ }
+ CollectionDockingView.AddSplit(googleDoc, 'right');
+ } else if (e.altKey) {
+ e.preventDefault();
+ window.open(googleDocUrl);
+ } else {
+ this.clearPullColor();
+ DocumentButtonBar.hasPulledHack = false;
+ targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+ dataDoc.googleDocUnchanged && runInAction(() => (this.isAnimatingFetch = true));
}
- })()}
- />
- </div></Tooltip>;
+ }}>
+ <FontAwesomeIcon
+ className="documentdecorations-icon"
+ size="sm"
+ style={{ WebkitAnimation: animation, MozAnimation: animation }}
+ icon={(() => {
+ switch (this.openHover) {
+ default:
+ case UtilityButtonState.Default:
+ return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch;
+ case UtilityButtonState.OpenRight:
+ return 'arrow-alt-circle-right';
+ case UtilityButtonState.OpenExternally:
+ return 'share';
+ }
+ })()}
+ />
+ </div>
+ </Tooltip>
+ );
}
@computed
get followLinkButton() {
const targetDoc = this.view0?.props.Document;
- return !targetDoc ? (null) : <Tooltip title={
- <div className="dash-tooltip">{"Set onClick to follow primary link"}</div>}>
- <div className="documentButtonBar-icon"
- style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
- onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
- </div>
- </Tooltip>;
+ return !targetDoc ? null : (
+ <Tooltip title={<div className="dash-tooltip">{'Set onClick to follow primary link'}</div>}>
+ <div
+ className="documentButtonBar-icon"
+ style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
+ onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
+ </div>
+ </Tooltip>
+ );
}
@computed
get pinButton() {
const targetDoc = this.view0?.props.Document;
- return !targetDoc ? (null) : <Tooltip title={
- <div className="dash-tooltip">{SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}</div>}>
- <div className="documentButtonBar-icon"
- style={{ color: "white" }}
- onClick={undoBatch(e => this.props.views().map(view => view && this.pinWithView(view.props.Document)))}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
- </div>
- </Tooltip>;
- }
-
- @undoBatch
- @action
- pinWithView = (targetDoc: Doc) => {
- if (targetDoc) {
- TabDocView.PinDoc(targetDoc);
- const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
- const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking;
- const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
- if (scrollable) {
- const scroll = targetDoc._scrollTop;
- activeDoc.presPinView = true;
- activeDoc.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeDoc.presPinTimecode = targetDoc._currentTimecode;
- } else if (pannable) {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeDoc.presPinView = true;
- activeDoc.presPinViewX = x;
- activeDoc.presPinViewY = y;
- activeDoc.presPinViewScale = scale;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const width = targetDoc._clipWidth;
- activeDoc.presPinClipWidth = width;
- activeDoc.presPinView = true;
- }
- }
- }
-
- @computed
- get pinWithViewButton() {
- const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 17, transform: 'translate(0, 1px)' }} />;
- const targetDoc = this.view0?.props.Document;
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with current view"}</div></>}>
- <div className="documentButtonBar-icon" onClick={() => this.pinWithView(targetDoc)}>
- {presPinWithViewIcon}
- </div>
- </Tooltip>;
+ return !targetDoc ? null : (
+ <Tooltip title={<div className="dash-tooltip">{SelectionManager.Views().length > 1 ? 'Pin multiple documents to presentation' : 'Pin to presentation'}</div>}>
+ <div
+ className="documentButtonBar-icon"
+ style={{ color: 'white' }}
+ onClick={e =>
+ TabDocView.PinDoc(
+ this.props
+ .views()
+ .filter(v => v)
+ .map(dv => dv!.rootDoc),
+ { pinDocView: true }
+ )
+ }>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
+ </div>
+ </Tooltip>
+ );
}
@computed
get shareButton() {
const targetDoc = this.view0?.props.Document;
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Sharing Manager"}</div></>}>
- <div className="documentButtonBar-icon" style={{ color: "white" }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="users" />
- </div></Tooltip >;
+ return !targetDoc ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Open Sharing Manager'}</div>
+ </>
+ }>
+ <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="users" />
+ </div>
+ </Tooltip>
+ );
}
@computed
get menuButton() {
const targetDoc = this.view0?.props.Document;
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`Open Context Menu`}</div></>}>
- <div className="documentButtonBar-icon" style={{ color: "white", cursor: "pointer" }} onClick={e => this.openContextMenu(e)}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
- </div></Tooltip >;
+ return !targetDoc ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{`Open Context Menu`}</div>
+ </>
+ }>
+ <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={e => this.openContextMenu(e)}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
+ </div>
+ </Tooltip>
+ );
}
@computed
get moreButton() {
const targetDoc = this.view0?.props.Document;
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${CurrentUserUtils.propertiesWidth > 0 ? "Close" : "Open"} Properties Panel`}</div></>}>
- <div className="documentButtonBar-icon" style={{ color: "white", cursor: "e-resize" }} onClick={action(e =>
- CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="ellipsis-h"
- />
- </div></Tooltip >;
+ return !targetDoc ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{`${SettingsManager.propertiesWidth > 0 ? 'Close' : 'Open'} Properties Panel`}</div>
+ </>
+ }>
+ <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'e-resize' }} onClick={action(e => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250))}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="ellipsis-h" />
+ </div>
+ </Tooltip>
+ );
}
@computed
get metadataButton() {
const view0 = this.view0;
- return !view0 ? (null) : <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}>
- <div className="documentButtonBar-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP}
- content={<MetadataEntryMenu docs={this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
- <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />}
- </div>
- </Flyout>
- </div></Tooltip>;
+ return !view0 ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">Show metadata panel</div>
+ </>
+ }>
+ <div className="documentButtonBar-linkFlyout">
+ <Flyout
+ anchorPoint={anchorPoints.LEFT_TOP}
+ content={
+ <MetadataEntryMenu
+ docs={this.props
+ .views()
+ .filter(dv => dv)
+ .map(dv => dv!.props.Document)}
+ suggestWithFunction
+ /> /* tfs: @bcz This might need to be the data document? */
+ }>
+ <div className={'documentButtonBar-linkButton-' + 'empty'} onPointerDown={e => e.stopPropagation()}>
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />}
+ </div>
+ </Flyout>
+ </div>
+ </Tooltip>
+ );
}
@observable _aliasDown = false;
- onAliasButtonDown = action((e: React.PointerEvent): void => {
- this.props.views()[0]?.select(false);
+ onTemplateButton = action((e: React.PointerEvent): void => {
this._tooltipOpen = false;
setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
});
@@ -304,13 +344,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const dragDocView = this.view0!;
const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- dragData.defaultDropAction = "alias";
+ dragData.defaultDropAction = 'alias';
dragData.canEmbed = true;
DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
return true;
}
return false;
- }
+ };
_ref = React.createRef<HTMLDivElement>();
@observable _tooltipOpen: boolean = false;
@@ -318,81 +358,84 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get templateButton() {
const view0 = this.view0;
const views = this.props.views();
- return !view0 ? (null) :
- <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom">
- <div className="documentButtonBar-linkFlyout" ref={this._dragRef}
- onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} >
-
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
- content={!this._aliasDown ? (null) :
- <div ref={this._ref}> <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} /></div>}>
- <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+ return !view0 ? null : (
+ <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
+ <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
+ <Flyout
+ anchorPoint={anchorPoints.LEFT_TOP}
+ onOpen={action(() => (this._aliasDown = true))}
+ onClose={action(() => (this._aliasDown = false))}
+ content={
+ !this._aliasDown ? null : (
+ <div ref={this._ref}>
+ {' '}
+ <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} />
+ </div>
+ )
+ }>
+ <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}>
{<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
</div>
</Flyout>
</div>
- </Tooltip>;
+ </Tooltip>
+ );
}
openContextMenu = (e: React.MouseEvent) => {
let child = SelectionManager.Views()[0].ContentDiv!.children[0];
while (child.children.length) {
- const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string");
+ const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
if (next?.className?.toString().includes(DashFieldView.name)) break;
if (next) child = next;
else break;
}
simulateMouseClick(child, e.clientX, e.clientY - 30, e.screenX, e.screenY - 30);
- }
+ };
render() {
- if (!this.view0) return (null);
+ if (!this.view0) return null;
const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField;
const doc = this.view0?.props.Document;
const considerPull = isText && this.considerGoogleDocsPull;
const considerPush = isText && this.considerGoogleDocsPush;
- return <div className="documentButtonBar">
- <div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
- </div>
- {(DocumentLinksButton.StartLink || Doc.UserDoc()["documentLinksButton-fullMenu"]) && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
- </div> : (null)}
- {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
- {this.templateButton}
- </div>
- /*<div className="documentButtonBar-button">
+ return (
+ <div className="documentButtonBar">
+ <div className="documentButtonBar-button">
+ <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+ </div>
+ {(DocumentLinksButton.StartLink || Doc.UserDoc()['documentLinksButton-fullMenu']) && DocumentLinksButton.StartLink !== doc ? (
+ <div className="documentButtonBar-button">
+ <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+ </div>
+ ) : null}
+ {
+ Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
+ /*<div className="documentButtonBar-button">
{this.metadataButton}
</div>
<div className="documentButtonBar-button">
{this.contextButton}
- </div> */}
- {!SelectionManager.Views()?.some(v => v.allLinks.length) ? (null) : <div className="documentButtonBar-button">
- {this.followLinkButton}
- </div>}
- <div className="documentButtonBar-button">
- {this.pinButton}
- </div>
- {/* <div className="documentButtonBar-button">
- {this.pinWithViewButton}
- </div> */}
- {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
- {this.shareButton}
- </div>}
- {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
- {this.considerGoogleDocsPush}
- </div>}
- <div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
- {this.considerGoogleDocsPull}
- </div>
- <div className="documentButtonBar-button">
- {this.menuButton}
- </div>
- {/* {Doc.UserDoc().noviceMode ? (null) : <div className="documentButtonBar-button">
+ </div> */
+ }
+ {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
+ <div className="documentButtonBar-button">{this.pinButton}</div>
+ {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
+ {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : (
+ <div className="documentButtonBar-button" style={{ display: !considerPush ? 'none' : '' }}>
+ {this.considerGoogleDocsPush}
+ </div>
+ )}
+ <div className="documentButtonBar-button" style={{ display: !considerPull ? 'none' : '' }}>
+ {this.considerGoogleDocsPull}
+ </div>
+ <div className="documentButtonBar-button">{this.menuButton}</div>
+ {/* {Doc.noviceMode ? (null) : <div className="documentButtonBar-button">
{this.moreButton}
</div>} */}
- </div>;
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 82dca1287..b490278c3 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,11 +1,13 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables';
$linkGap: 3px;
+$headerHeight: 20px;
+$resizeHandler: 8px;
.documentDecorations-Dark,
.documentDecorations {
- position: absolute;
- z-index: 2000;
+ position: absolute;
+ z-index: 2000;
}
.documentDecorations-Dark {
background: dimgray;
@@ -16,12 +18,12 @@ $linkGap: 3px;
top: 0;
left: 0;
display: grid;
- grid-template-rows: 20px 8px 1fr 8px;
- grid-template-columns: 8px 16px 1fr 8px 8px;
+ grid-template-rows: $headerHeight $resizeHandler 1fr $resizeHandler;
+ grid-template-columns: $resizeHandler 1fr $resizeHandler;
pointer-events: none;
.documentDecorations-centerCont {
- grid-column: 3;
+ grid-column: 2;
background: none;
}
@@ -41,6 +43,7 @@ $linkGap: 3px;
opacity: 1;
transform: translate(10px, 10px);
grid-row: 4;
+ grid-column: 3;
}
.documentDecorations-topLeftResizer,
@@ -59,8 +62,7 @@ $linkGap: 3px;
}
}
- .documentDecorations-resizer-Dark
- {
+ .documentDecorations-resizer-Dark {
background: $light-gray;
opacity: 0.2;
}
@@ -68,47 +70,100 @@ $linkGap: 3px;
.documentDecorations-topLeftResizer,
.documentDecorations-leftResizer,
.documentDecorations-bottomLeftResizer {
- grid-column: 1
+ grid-column: 1;
}
.documentDecorations-topResizer,
.documentDecorations-bottomResizer {
- grid-column-start: 2;
- grid-column-end: 5;
+ grid-column: 2;
}
.documentDecorations-bottomRightResizer,
.documentDecorations-topRightResizer,
.documentDecorations-rightResizer {
- grid-column-start: 5;
- grid-column-end: 7;
+ grid-column: 3;
}
- .documentDecorations-rotation,
+ // Rotation handler
+ .documentDecorations-rotation {
+ border-radius: 100%;
+ height: 30;
+ width: 30;
+ right: -10;
+ top: 50%;
+ z-index: 1000000;
+ position: absolute;
+ pointer-events: all;
+ cursor: pointer;
+ background: white;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ font-size: 30px;
+ }
+
+ // Border radius handler
.documentDecorations-borderRadius {
- grid-column: 5;
- grid-row: 4;
+ position: absolute;
border-radius: 100%;
- background: black;
- height: 8;
- right: -12;
- top: 12;
- position: relative;
+ left: 3px;
+ top: 23px;
+ background: $medium-gray;
+ height: 10;
+ width: 10;
pointer-events: all;
cursor: nwse-resize;
+ }
- .borderRadiusTooltip {
- width: 10px;
- height: 10px;
- position: absolute;
- }
+ .documentDecorations-rotationPath {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ transform: translate(0px, -25%);
+ padding-bottom: 100%;
+ border-radius: 100%;
+ border: solid $medium-gray 10px;
}
- .documentDecorations-rotation {
- background: transparent;
- right: -15;
+ .documentDecorations-topLeftResizer,
+ .documentDecorations-bottomRightResizer {
+ cursor: nwse-resize;
+ background: unset;
+ opacity: 1;
}
+ .documentDecorations-topLeftResizer {
+ border-left: 2px solid;
+ border-top: solid 2px;
+ }
+
+ .documentDecorations-bottomRightResizer {
+ border-right: 2px solid;
+ border-bottom: solid 2px;
+ }
+
+ .documentDecorations-topLeftResizer:hover,
+ .documentDecorations-bottomRightResizer:hover {
+ opacity: 1;
+ }
+
+ .documentDecorations-topLeftResizer,
+ .documentDecorations-leftResizer,
+ .documentDecorations-bottomLeftResizer {
+ grid-column: 1;
+ }
+
+ .documentDecorations-topResizer,
+ .documentDecorations-bottomResizer {
+ grid-column: 2;
+ }
+
+ .documentDecorations-bottomRightResizer,
+ .documentDecorations-topRightResizer,
+ .documentDecorations-rightResizer {
+ grid-column: 3;
+ }
.documentDecorations-topLeftResizer,
.documentDecorations-bottomRightResizer {
@@ -132,121 +187,63 @@ $linkGap: 3px;
opacity: 1;
}
- .documentDecorations-topLeftResizer,
- .documentDecorations-leftResizer,
- .documentDecorations-bottomLeftResizer {
- grid-column: 1;
- }
-
- .documentDecorations-topResizer,
- .documentDecorations-bottomResizer {
- grid-column-start: 2;
- grid-column-end: 5;
- }
-
- .documentDecorations-bottomRightResizer,
- .documentDecorations-topRightResizer,
- .documentDecorations-rightResizer {
- grid-column-start: 5;
- grid-column-end: 7;
- }
-
- .documentDecorations-rotation,
- .documentDecorations-borderRadius {
- grid-column: 5;
- grid-row: 4;
- border-radius: 100%;
- background: black;
- height: 8;
- right: -12;
- top: 12;
- position: relative;
- pointer-events: all;
- cursor: nwse-resize;
-
- .borderRadiusTooltip {
- width: 10px;
- height: 10px;
- position: absolute;
- }
- }
- .documentDecorations-rotation {
- background: transparent;
- right: -15;
- }
-
- .documentDecorations-topLeftResizer,
- .documentDecorations-bottomRightResizer {
- cursor: nwse-resize;
- background: unset;
- opacity: 1;
- }
+ .documentDecorations-bottomRightResizer {
+ grid-row: 4;
+ }
- .documentDecorations-topLeftResizer {
- border-left: 2px solid;
- border-top: solid 2px;
- }
+ .documentDecorations-topRightResizer,
+ .documentDecorations-bottomLeftResizer {
+ cursor: nesw-resize;
+ background: unset;
+ opacity: 1;
+ }
- .documentDecorations-bottomRightResizer {
- border-right: 2px solid;
- border-bottom: solid 2px;
- }
+ .documentDecorations-topRightResizer {
+ border-right: 2px solid;
+ border-top: 2px solid;
+ }
- .documentDecorations-topLeftResizer:hover,
- .documentDecorations-bottomRightResizer:hover {
- opacity: 1;
- }
+ .documentDecorations-bottomLeftResizer {
+ border-left: 2px solid;
+ border-bottom: 2px solid;
+ }
- .documentDecorations-bottomRightResizer {
- grid-row: 4;
- }
+ .documentDecorations-topRightResizer:hover,
+ .documentDecorations-bottomLeftResizer:hover {
+ cursor: nesw-resize;
+ background: black;
+ opacity: 1;
+ }
- .documentDecorations-topRightResizer,
- .documentDecorations-bottomLeftResizer {
- cursor: nesw-resize;
- background: unset;
- opacity: 1;
- }
-
- .documentDecorations-topRightResizer {
- border-right: 2px solid;
- border-top: 2px solid;
- }
-
- .documentDecorations-bottomLeftResizer {
- border-left: 2px solid;
- border-bottom: 2px solid;
- }
-
- .documentDecorations-topRightResizer:hover,
- .documentDecorations-bottomLeftResizer:hover {
- cursor: nesw-resize;
- background: black;
- opacity: 1;
- }
+ .documentDecorations-topResizer,
+ .documentDecorations-bottomResizer {
+ cursor: ns-resize;
+ }
- .documentDecorations-topResizer,
- .documentDecorations-bottomResizer {
- cursor: ns-resize;
- }
+ .documentDecorations-leftResizer,
+ .documentDecorations-rightResizer {
+ cursor: ew-resize;
+ }
.documentDecorations-title-Dark,
.documentDecorations-title {
opacity: 1;
- grid-column-start: 2;
- grid-column-end: 4;
+ width: calc(100% - 8px); // = margin-left + margin-right
+ grid-column: 2;
+ grid-column-end: 2;
pointer-events: auto;
overflow: hidden;
text-align: center;
display: flex;
- margin-left: 5px;
+ margin-left: 6px; // closeButton width (14) - leftColumn width (8)
+ margin-right: 2px;
height: 20px;
position: absolute;
border-radius: 8px;
- background: rgba(159,159,159,0.1);
+ background: rgba(159, 159, 159, 0.1);
- .documentDecorations-titleSpan,
- .documentDecorations-titleSpan-Dark {
+ .documentDecorations-titleSpan,
+ .documentDecorations-titleSpan-Dark {
width: 100%;
border-radius: 8px;
background: #ffffffa0;
@@ -263,105 +260,62 @@ $linkGap: 3px;
background: black;
}
- .documentDecorations-contextMenu {
- width: 25px;
- height: calc(100% + 8px); // 8px for the height of the top resizer bar
- grid-column-start: 2;
- grid-column-end: 2;
- pointer-events: all;
- padding-left: 5px;
- cursor: pointer;
- }
-
- .documentDecorations-titleBackground {
- background: #ffffffcf;
- border-radius: 8px;
- width: 100%;
- height: 100%;
- position: absolute;
- }
-
- .documentDecorations-title {
- opacity: 1;
- grid-column-start: 2;
- grid-column-end: 4;
- pointer-events: auto;
- overflow: hidden;
- text-align: center;
- display: flex;
- margin-left: 5px;
- height: 20px;
- position: absolute;
- .documentDecorations-titleSpan {
- width: 100%;
- border-radius: 8px;
- background: #ffffffcf;
- position: absolute;
- display: inline-block;
- cursor: move;
- }
- }
-
- .focus-visible {
- margin-left: 0px;
- }
-}
+ .documentDecorations-titleBackground {
+ background: #ffffffcf;
+ border-radius: 8px;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ }
-.documentDecorations-iconifyButton {
- opacity: 1;
- grid-column-start: 4;
- grid-column-end: 4;
- pointer-events: all;
- right: 0;
- cursor: pointer;
- position: absolute;
- width: 20px;
+ .focus-visible {
+ margin-left: 0px;
+ }
}
.documentDecorations-openButton {
- display: flex;
- align-items: center;
- opacity: 1;
- grid-column-start: 5;
- grid-column-end: 5;
- pointer-events: all;
- cursor: pointer;
+ display: flex;
+ align-items: center;
+ opacity: 1;
+ grid-column-start: 3;
+ pointer-events: all;
+ cursor: pointer;
}
.documentDecorations-closeButton {
- display: flex;
- align-items: center;
- opacity: 1;
- grid-column-start: 1;
- grid-column-end: 3;
- pointer-events: all;
- cursor: pointer;
-
- > svg {
- margin: 0;
- }
+ display: flex;
+ align-items: center;
+ opacity: 1;
+ grid-column: 1;
+ pointer-events: all;
+ width: 14px;
+ cursor: pointer;
+
+ > svg {
+ margin: 0;
+ }
}
.documentDecorations-background {
- background: lightblue;
- position: absolute;
- opacity: 0.1;
+ background: lightblue;
+ position: absolute;
+ opacity: 0.1;
}
.linkFlyout {
- grid-column: 2/4;
+ grid-column: 2/4;
}
.linkButton-empty:hover {
- background: $medium-gray;
- transform: scale(1.05);
- cursor: pointer;
+ background: $medium-gray;
+ transform: scale(1.05);
+ cursor: pointer;
}
.linkButton-nonempty:hover {
- background: $medium-gray;
- transform: scale(1.05);
- cursor: pointer;
+ background: $medium-gray;
+ transform: scale(1.05);
+ cursor: pointer;
}
.link-button-container {
@@ -379,132 +333,132 @@ $linkGap: 3px;
}
.linkButtonWrapper {
- pointer-events: auto;
- padding-right: 5px;
- width: 25px;
+ pointer-events: auto;
+ padding-right: 5px;
+ width: 25px;
}
.linkButton-linker {
- height: 20px;
- width: 20px;
- text-align: center;
- border-radius: 50%;
- pointer-events: auto;
- color: $dark-gray;
- border: $dark-gray 1px solid;
+ height: 20px;
+ width: 20px;
+ text-align: center;
+ border-radius: 50%;
+ pointer-events: auto;
+ color: $dark-gray;
+ border: $dark-gray 1px solid;
}
.linkButton-linker:hover {
- cursor: pointer;
- transform: scale(1.05);
+ cursor: pointer;
+ transform: scale(1.05);
}
.linkButton-empty,
.linkButton-nonempty {
- height: 20px;
- width: 20px;
- border-radius: 50%;
- opacity: 0.9;
- pointer-events: auto;
- background-color: $dark-gray;
- color: $white;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
-
- &:hover {
- background: $medium-gray;
- transform: scale(1.05);
- cursor: pointer;
- }
+ height: 20px;
+ width: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ pointer-events: auto;
+ background-color: $dark-gray;
+ color: $white;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ background: $medium-gray;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
}
.templating-menu {
- position: absolute;
- pointer-events: auto;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
+ position: absolute;
+ pointer-events: auto;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
}
.documentdecorations-icon {
- margin: 0px;
+ margin: 0px;
}
.templating-button,
.docDecs-tagButton {
- width: 20px;
- height: 20px;
- border-radius: 50%;
- opacity: 0.9;
- font-size: 14;
- background-color: $dark-gray;
- color: $white;
- text-align: center;
- cursor: pointer;
-
- &:hover {
- background: $medium-gray;
- transform: scale(1.05);
- }
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ font-size: 14;
+ background-color: $dark-gray;
+ color: $white;
+ text-align: center;
+ cursor: pointer;
+
+ &:hover {
+ background: $medium-gray;
+ transform: scale(1.05);
+ }
}
#template-list {
- position: absolute;
- top: 25px;
- left: 0px;
- width: max-content;
- font-family: $sans-serif;
- font-size: 12px;
- background-color: $light-gray;
- padding: 2px 12px;
- list-style: none;
-
- .templateToggle,
- .chromeToggle {
- text-align: left;
- }
-
- input {
- margin-right: 10px;
- }
+ position: absolute;
+ top: 25px;
+ left: 0px;
+ width: max-content;
+ font-family: $sans-serif;
+ font-size: 12px;
+ background-color: $light-gray;
+ padding: 2px 12px;
+ list-style: none;
+
+ .templateToggle,
+ .chromeToggle {
+ text-align: left;
+ }
+
+ input {
+ margin-right: 10px;
+ }
}
@-moz-keyframes spin {
- 100% {
- -moz-transform: rotate(360deg);
- }
+ 100% {
+ -moz-transform: rotate(360deg);
+ }
}
@-webkit-keyframes spin {
- 100% {
- -webkit-transform: rotate(360deg);
- }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ }
}
@keyframes spin {
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
}
@keyframes shadow-pulse {
- 0% {
- box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8);
- }
+ 0% {
+ box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8);
+ }
- 100% {
- box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
- }
+ 100% {
+ box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
+ }
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d0b5bfe46..0db9eab69 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,219 +1,357 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
+import { action, computed, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
import { DateField } from '../../fields/DateField';
-import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from "../../fields/Doc";
+import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc';
import { Document } from '../../fields/documentSchemas';
-import { InkField } from "../../fields/InkField";
+import { InkField } from '../../fields/InkField';
import { ScriptField } from '../../fields/ScriptField';
-import { Cast, NumCast, StrCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
-import { Docs } from "../documents/Documents";
+import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { DragManager } from "../util/DragManager";
-import { SelectionManager } from "../util/SelectionManager";
+import { DragManager } from '../util/DragManager';
+import { SelectionManager } from '../util/SelectionManager';
import { SnappingManager } from '../util/SnappingManager';
-import { undoBatch, UndoManager } from "../util/UndoManager";
+import { undoBatch, UndoManager } from '../util/UndoManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
-import { KeyManager } from './GlobalKeyHandler';
+import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
-import { DocumentView } from "./nodes/DocumentView";
+import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import React = require("react");
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { ImageBox } from './nodes/ImageBox';
+import React = require('react');
@observer
-export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> {
+export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
static Instance: DocumentDecorations;
- private _resizeHdlId = "";
+ private _resizeHdlId = '';
private _keyinput = React.createRef<HTMLInputElement>();
private _resizeBorderWidth = 16;
private _linkBoxHeight = 20 + 3; // link button height + margin
private _titleHeight = 20;
private _resizeUndo?: UndoManager.Batch;
- private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border
- private _snapX = 0; _snapY = 0; // last snapped location of resize border
- private _dragHeights = new Map<Doc, { start: number, lowest: number }>();
- private _inkDragDocs: { doc: Doc, x: number, y: number, width: number, height: number }[] = [];
-
- @observable private _accumulatedTitle = "";
- @observable private _titleControlString: string = "#title";
- @observable private _edtingTitle = false;
+ private _offX = 0;
+ _offY = 0; // offset from click pt to inner edge of resize border
+ private _snapX = 0;
+ _snapY = 0; // last snapped location of resize border
+ private _dragHeights = new Map<Doc, { start: number; lowest: number }>();
+ private _inkDragDocs: { doc: Doc; x: number; y: number; width: number; height: number }[] = [];
+
+ @observable private _accumulatedTitle = '';
+ @observable private _titleControlString: string = '#title';
+ @observable private _editingTitle = false;
@observable private _hidden = false;
-
+ @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set.
@observable public Interacting = false;
- @observable public pushIcon: IconProp = "arrow-alt-circle-up";
- @observable public pullIcon: IconProp = "arrow-alt-circle-down";
- @observable public pullColor: string = "white";
+ @observable public pushIcon: IconProp = 'arrow-alt-circle-up';
+ @observable public pullIcon: IconProp = 'arrow-alt-circle-down';
+ @observable public pullColor: string = 'white';
+ @observable private _isRotating: boolean = false;
+ @observable private _isRounding: boolean = false;
+ @observable private _isResizing: boolean = false;
constructor(props: any) {
super(props);
DocumentDecorations.Instance = this;
- reaction(() => SelectionManager.Views().slice(), action(docs => this._edtingTitle = false));
+ reaction(
+ () => SelectionManager.Views().slice(),
+ action(docs => (this._editingTitle = false))
+ );
}
@computed
get Bounds() {
const views = SelectionManager.Views();
- return views.map(dv => dv.getBounds()).reduce((bounds, rect) =>
- !rect ? bounds :
- {
- x: Math.min(rect.left, bounds.x),
- y: Math.min(rect.top, bounds.y),
- r: Math.max(rect.right, bounds.r),
- b: Math.max(rect.bottom, bounds.b),
- c: views.length === 1 ? rect.center : undefined
- },
- { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) });
+ return views
+ .filter(dv => dv.props.renderDepth > 0)
+ .map(dv => dv.getBounds())
+ .reduce(
+ (bounds, rect) =>
+ !rect
+ ? bounds
+ : {
+ x: Math.min(rect.left, bounds.x),
+ y: Math.min(rect.top, bounds.y),
+ r: Math.max(rect.right, bounds.r),
+ b: Math.max(rect.bottom, bounds.b),
+ c: views.length === 1 ? rect.center : undefined,
+ },
+ { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as { X: number; Y: number } | undefined }
+ );
}
@action
titleBlur = () => {
- this._edtingTitle = false;
- if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
+ this._editingTitle = false;
+ if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
this._titleControlString = this._accumulatedTitle;
- } else if (this._titleControlString.startsWith("#")) {
+ } else if (this._titleControlString.startsWith('#')) {
const titleFieldKey = this._titleControlString.substring(1);
- UndoManager.RunInBatch(() => titleFieldKey && SelectionManager.Views().forEach(d => {
- titleFieldKey === "title" && (d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"));
- //@ts-ignore
- const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle);
- Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
-
- if (d.rootDoc.syncLayoutFieldWithTitle) {
- const title = titleField.toString();
- const curKey = Doc.LayoutFieldKey(d.rootDoc);
- if (curKey !== title && d.dataDoc[title] === undefined) {
- d.rootDoc.layout = FormattedTextBox.LayoutString(title);
- setTimeout(() => {
- const val = d.dataDoc[curKey];
- d.dataDoc[curKey] = undefined;
- d.dataDoc[title] = val;
- });
- }
- }
- }), "title blur");
+ UndoManager.RunInBatch(
+ () =>
+ titleFieldKey &&
+ SelectionManager.Views().forEach(d => {
+ if (titleFieldKey === 'title') {
+ d.dataDoc['title-custom'] = !this._accumulatedTitle.startsWith('-');
+ if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
+ Doc.RemoveDocFromList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+ }
+ if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
+ Doc.AddDocToList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+ }
+ }
+ //@ts-ignore
+ const titleField = +this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
+ Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
+
+ if (d.rootDoc.syncLayoutFieldWithTitle) {
+ const title = titleField.toString();
+ const curKey = Doc.LayoutFieldKey(d.rootDoc);
+ if (curKey !== title && d.dataDoc[title] === undefined) {
+ d.rootDoc.layout = FormattedTextBox.LayoutString(title);
+ setTimeout(() => {
+ const val = d.dataDoc[curKey];
+ d.dataDoc[curKey] = undefined;
+ d.dataDoc[title] = val;
+ });
+ }
+ }
+ }),
+ 'title blur'
+ );
}
- }
+ };
- titleEntered = (e: React.KeyboardEvent) => e.key === "Enter" && (e.target as any).blur();
+ titleEntered = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.stopPropagation();
+ (e.target as any).blur();
+ }
+ };
@action onTitleDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
- !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString);
- this._edtingTitle = true;
- this._keyinput.current && setTimeout(this._keyinput.current.focus);
- }));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ e => this.onBackgroundMove(true, e),
+ e => {},
+ action(e => {
+ !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+ this._editingTitle = true;
+ this._keyinput.current && setTimeout(this._keyinput.current.focus);
+ })
+ );
+ };
onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
@action
onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
const dragDocView = SelectionManager.Views()[0];
+ if (DocListCast(Doc.MyOverlayDocs.data).includes(dragDocView.rootDoc)) return false;
const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
- const dragData = new DragManager.DocumentDragData(SelectionManager.Views().map(dv => dv.props.Document), dragDocView.props.dropAction);
+ const dragData = new DragManager.DocumentDragData(
+ SelectionManager.Views().map(dv => dv.props.Document),
+ dragDocView.props.dropAction
+ );
dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
dragData.moveDocument = dragDocView.props.moveDocument;
dragData.isDocDecorationMove = true;
dragData.canEmbed = dragTitle;
this._hidden = this.Interacting = true;
- DragManager.StartDocumentDrag(SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, e.x, e.y, {
- dragComplete: action(e => {
- dragData.canEmbed && SelectionManager.DeselectAll();
- this._hidden = this.Interacting = false;
- }),
- hideSource: true
- });
+ DragManager.StartDocumentDrag(
+ SelectionManager.Views().map(dv => dv.ContentDiv!),
+ dragData,
+ e.x,
+ e.y,
+ {
+ dragComplete: action(e => {
+ dragData.canEmbed && SelectionManager.DeselectAll();
+ this._hidden = this.Interacting = false;
+ }),
+ hideSource: true,
+ }
+ );
return true;
- }
-
- onCloseClick = () => {
- const selected = SelectionManager.Views().slice();
- SelectionManager.DeselectAll();
- selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
- }
+ };
+
+ _deleteAfterIconify = false;
+ _iconifyBatch: UndoManager.Batch | undefined;
+ onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
+ const views = SelectionManager.Views()
+ .slice()
+ .filter(v => v);
+ if (forceDeleteOrIconify === false && this._iconifyBatch) return;
+ this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
+ if (!this._iconifyBatch) {
+ this._iconifyBatch = UndoManager.StartBatch('iconifying');
+ } else {
+ forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
+ }
+ var iconifyingCount = views.length;
+ const finished = action((force?: boolean) => {
+ if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
+ if (this._deleteAfterIconify) {
+ views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document));
+ SelectionManager.DeselectAll();
+ }
+ this._iconifyBatch?.end();
+ this._iconifyBatch = undefined;
+ }
+ });
+ if (forceDeleteOrIconify) finished(forceDeleteOrIconify);
+ else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished));
+ };
onMaximizeDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, () => {
- DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, [SelectionManager.Views().slice(-1)[0].rootDoc]);
- return true;
- }, emptyFunction, this.onMaximizeClick, false, false);
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ () => {
+ DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
+ return true;
+ },
+ emptyFunction,
+ this.onMaximizeClick,
+ false,
+ false
+ );
+ };
onMaximizeClick = (e: any): void => {
const selectedDocs = SelectionManager.Views();
if (selectedDocs.length) {
- if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key
+ if (e.ctrlKey) {
+ // open an alias in a new tab with Ctrl Key
const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
- CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right");
- } else if (e.shiftKey) { // open centered in a new workspace with Shift Key
+ CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), 'right');
+ } else if (e.shiftKey) {
+ // open centered in a new workspace with Shift Key
const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
alias.context = undefined;
alias.x = -alias[WidthSym]() / 2;
alias.y = -alias[HeightSym]() / 2;
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
- } else if (e.altKey) { // open same document in new tab
- CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), 'right');
+ } else if (e.altKey) {
+ // open same document in new tab
+ CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, 'right');
} else {
- LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document));
+ var openDoc = selectedDocs[0].props.Document;
+ if (openDoc.layoutKey === 'layout_icon') {
+ openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
+ Doc.deiconifyView(openDoc);
+ }
+ LightboxView.SetLightboxDoc(
+ openDoc,
+ undefined,
+ selectedDocs.slice(1).map(view => view.props.Document)
+ );
}
}
SelectionManager.DeselectAll();
- }
+ };
onIconifyClick = (): void => {
SelectionManager.Views().forEach(dv => dv?.iconify());
SelectionManager.DeselectAll();
- }
+ };
onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
+ /**
+ * Handles setting up events when user clicks on the border radius editor
+ * @param e PointerEvent
+ */
+ @action
onRadiusDown = (e: React.PointerEvent): void => {
- this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
- setupMoveUpEvents(this, e, (e, down) => {
- const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
- SelectionManager.Views().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)).
- map(d => d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`);
- return false;
- }, (e) => this._resizeUndo?.end(), (e) => { });
- }
+ this._isRounding = true;
+ this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
+ // Call util move event function
+ setupMoveUpEvents(
+ this, // target
+ e, // pointerEvent
+ (e, down) => {
+ const x = this.Bounds.x + 3;
+ const y = this.Bounds.y + 3;
+ const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
+ let dist = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
+ if (e.clientX < x && e.clientY < y) dist = 0;
+ SelectionManager.Views()
+ .map(dv => dv.props.Document)
+ .map(doc => {
+ const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
+ const ratio = dist / maxDist;
+ const radius = Math.min(1, ratio) * docMax;
+ doc.borderRounding = `${radius}px`;
+ });
+ return false;
+ }, // moveEvent
+ action(e => {
+ this._isRounding = false;
+ this._resizeUndo?.end();
+ }), // upEvent
+ e => {} // clickEvent
+ );
+ };
@action
onRotateDown = (e: React.PointerEvent): void => {
- const rotateUndo = UndoManager.StartBatch("rotatedown");
- const centerPoint = { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 };
- setupMoveUpEvents(this, e,
+ this._isRotating = true;
+ const rotateUndo = UndoManager.StartBatch('rotatedown');
+ const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
+ const centerPoint = { X: (this.Bounds.x + this.Bounds.r) / 2, Y: (this.Bounds.y + this.Bounds.b) / 2 };
+ setupMoveUpEvents(
+ this,
+ e,
(e: PointerEvent, down: number[], delta: number[]) => {
const previousPoint = { X: e.clientX, Y: e.clientY };
const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
const angle = InkStrokeProperties.angleChange(previousPoint, movedPoint, centerPoint);
- const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
- angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
+ if (selectedInk.length) {
+ angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
+ } else {
+ SelectionManager.Views().forEach(dv => {
+ const oldRotation = NumCast(dv.rootDoc._jitterRotation);
+ // Rotation between -360 and 360
+ let newRotation = (oldRotation - (angle * 180) / Math.PI) % 360;
+
+ const diff = Math.round(newRotation / 45) - newRotation / 45;
+ if (diff < 0.05) {
+ console.log('show lines');
+ }
+ dv.rootDoc._jitterRotation = newRotation;
+ });
+ }
return false;
- },
- () => {
+ }, // moveEvent
+ action(() => {
+ SelectionManager.Views().forEach(dv => {
+ const oldRotation = NumCast(dv.rootDoc._jitterRotation);
+ const diff = Math.round(oldRotation / 45) - oldRotation / 45;
+ if (diff < 0.05) {
+ let newRotation = Math.round(oldRotation / 45) * 45;
+ dv.rootDoc._jitterRotation = newRotation;
+ }
+ });
+ this._isRotating = false;
rotateUndo?.end();
- UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
- },
- emptyFunction);
- }
+ UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
+ }), // upEvent
+ emptyFunction
+ );
+ };
@action
onPointerDown = (e: React.PointerEvent): void => {
- DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc);
+ DragManager.docsBeingDragged.push(...SelectionManager.Views().map(dv => dv.rootDoc));
this._inkDragDocs = DragManager.docsBeingDragged
.filter(doc => doc.type === DocumentType.INK)
.map(doc => {
@@ -221,16 +359,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
Doc.SetNativeHeight(doc, NumCast(doc._height));
Doc.SetNativeWidth(doc, NumCast(doc._width));
}
- return ({ doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
+ return { doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) };
});
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
this._resizeHdlId = e.currentTarget.className;
const bounds = e.currentTarget.getBoundingClientRect();
- this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX;
- this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY;
- this._resizeUndo = UndoManager.StartBatch("DocDecs resize");
+ this._offX = this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX;
+ this._offY = this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY;
+ this._resizeUndo = UndoManager.StartBatch('DocDecs resize');
this._snapX = e.pageX;
this._snapY = e.pageY;
const ffviewSet = new Set<CollectionFreeFormView>();
@@ -240,17 +378,21 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) });
});
Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false));
- }
+ };
onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
const first = SelectionManager.Views()[0];
+ if (!first) return false;
let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
var fixedAspect = Doc.NativeAspect(first.layoutDoc);
- InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
- .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
-
- const resizeHdl = this._resizeHdlId.split(" ")[0];
- if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
+ InkStrokeProperties.Instance._lock &&
+ SelectionManager.Views()
+ .filter(dv => dv.rootDoc.type === DocumentType.INK)
+ .forEach(dv => (fixedAspect = Doc.NativeAspect(dv.rootDoc)));
+
+ const resizeHdl = this._resizeHdlId.split(' ')[0];
+ if (fixedAspect && (resizeHdl === 'documentDecorations-bottomRightResizer' || resizeHdl === 'documentDecorations-topLeftResizer')) {
+ // need to generalize for bl and tr drag handles
const project = (p: number[], a: number[], b: number[]) => {
const atob = [b[0] - a[0], b[1] - a[1]];
const atop = [p[0] - a[0], p[1] - a[1]];
@@ -271,144 +413,161 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
move[1] = thisPt.y - this._snapY;
this._snapX = thisPt.x;
this._snapY = thisPt.y;
- let dragBottom = false, dragRight = false, dragBotRight = false;
- let dX = 0, dY = 0, dW = 0, dH = 0;
- switch (this._resizeHdlId.split(" ")[0]) {
- case "": break;
- case "documentDecorations-topLeftResizer":
+ let dragBottom = false,
+ dragRight = false,
+ dragBotRight = false,
+ dragTop = false;
+ let dX = 0,
+ dY = 0,
+ dW = 0,
+ dH = 0;
+ switch (this._resizeHdlId.split(' ')[0]) {
+ case '':
+ break;
+ case 'documentDecorations-topLeftResizer':
dX = -1;
dY = -1;
dW = -move[0];
dH = -move[1];
break;
- case "documentDecorations-topRightResizer":
+ case 'documentDecorations-topRightResizer':
dW = move[0];
dY = -1;
dH = -move[1];
break;
- case "documentDecorations-topResizer":
+ case 'documentDecorations-topResizer':
dY = -1;
dH = -move[1];
- dragBottom = true;
+ dragTop = true;
break;
- case "documentDecorations-bottomLeftResizer":
+ case 'documentDecorations-bottomLeftResizer':
dX = -1;
dW = -move[0];
dH = move[1];
break;
- case "documentDecorations-bottomRightResizer":
+ case 'documentDecorations-bottomRightResizer':
dW = move[0];
dH = move[1];
dragBotRight = true;
break;
- case "documentDecorations-bottomResizer":
+ case 'documentDecorations-bottomResizer':
dH = move[1];
dragBottom = true;
break;
- case "documentDecorations-leftResizer":
+ case 'documentDecorations-leftResizer':
dX = -1;
dW = -move[0];
break;
- case "documentDecorations-rightResizer":
+ case 'documentDecorations-rightResizer':
dW = move[0];
dragRight = true;
break;
}
- SelectionManager.Views().forEach(action((docView: DocumentView) => {
- if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
- if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = Document(docView.rootDoc);
- const nwidth = docView.nativeWidth;
- const nheight = docView.nativeHeight;
- const docheight = doc._height || 0;
- const docwidth = doc._width || 0;
- const width = docwidth;
- let height = (docheight || (nheight / nwidth * width));
- height = !height || isNaN(height) ? 20 : height;
- const scale = docView.props.ScreenToLocalTransform().Scale;
- const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
- if (nwidth && nheight) {
- if (nwidth / nheight !== width / height && !dragBottom) {
- height = nheight / nwidth * width;
- }
- if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
- if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
- else dW = dH * nwidth / nheight;
- }
- }
- let actualdW = Math.max(width + (dW * scale), 20);
- let actualdH = Math.max(height + (dH * scale), 20);
- const fixedAspect = (nwidth && nheight);
- if (fixedAspect) {
- if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
- if (dragRight && modifyNativeDim) {
- doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
- } else {
- if (!doc._fitWidth) {
- actualdH = nheight / nwidth * actualdW;
- doc._height = actualdH;
- }
- else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+ SelectionManager.Views().forEach(
+ action((docView: DocumentView) => {
+ if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+ const doc = Document(docView.rootDoc);
+ const nwidth = docView.nativeWidth;
+ const nheight = docView.nativeHeight;
+ let docheight = doc._height || 0;
+ let docwidth = doc._width || 0;
+ const width = docwidth;
+ let height = docheight || (nheight / nwidth) * width;
+ height = !height || isNaN(height) ? 20 : height;
+ const scale = docView.props.ScreenToLocalTransform().Scale;
+ const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen);
+ if (nwidth && nheight) {
+ if (nwidth / nheight !== width / height && !dragBottom && !dragTop) {
+ height = (nheight / nwidth) * width;
+ }
+ if (modifyNativeDim && !dragBottom && !dragTop) {
+ // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+ if (Math.abs(dW) > Math.abs(dH)) dH = (dW * nheight) / nwidth;
+ else dW = (dH * nwidth) / nheight;
}
- doc._width = actualdW;
}
- else {
- if (dragBottom && (modifyNativeDim ||
- (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
- doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
- doc._autoHeight = false;
+ let actualdW = Math.max(width + dW * scale, 20);
+ let actualdH = Math.max(height + dH * scale, 20);
+ const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen);
+ console.log(fixedAspect);
+ if (fixedAspect) {
+ if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
+ if (dragRight && modifyNativeDim) {
+ if (Doc.NativeWidth(doc)) {
+ doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc);
+ }
+ } else {
+ if (!doc._fitWidth) {
+ actualdH = (nheight / nwidth) * actualdW;
+ doc._height = actualdH;
+ } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+ }
+ doc._width = actualdW;
} else {
- if (!doc._fitWidth) {
- actualdW = nwidth / nheight * actualdH;
- doc._width = actualdW;
+ if ((dragBottom || dragTop) && (modifyNativeDim || (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) {
+ // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
+ doc._nativeHeight = (actualdH / (doc._height || 1)) * Doc.NativeHeight(doc);
+ doc._autoHeight = false;
+ } else {
+ if (!doc._fitWidth) {
+ actualdW = (nwidth / nheight) * actualdH;
+ doc._width = actualdW;
+ } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
}
- else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
- }
- if (!modifyNativeDim) {
- actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
- doc._height = actualdH;
+ if (!modifyNativeDim) {
+ actualdH = Math.min((nheight / nwidth) * NumCast(doc._width), actualdH);
+ doc._height = actualdH;
+ } else doc._height = actualdH;
}
- else doc._height = actualdH;
+ } else {
+ const maxHeight = 0; //Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
+ dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH);
+ dW && (doc._width = actualdW);
+ dH && (doc._autoHeight = false);
}
- } else {
- dH && (doc._height = actualdH);
- dW && (doc._width = actualdW);
- dH && (doc._autoHeight = false);
+ doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
+ doc.y = (doc.y || 0) + (dragBottom ? 0 : dY * (actualdH - docheight));
+ doc._lastModified = new DateField();
}
- doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
- doc.y = (doc.y || 0) + dY * (actualdH - docheight);
- doc._lastModified = new DateField();
- }
- const val = this._dragHeights.get(docView.layoutDoc);
- if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
- }));
+ const val = this._dragHeights.get(docView.layoutDoc);
+ if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
+ })
+ );
return false;
- }
+ };
@action
onPointerUp = (e: PointerEvent): void => {
- this._resizeHdlId = "";
+ this._resizeHdlId = '';
this.Interacting = false;
this._resizeUndo?.end();
SnappingManager.clearSnapLines();
// detect autoHeight gesture and apply
- SelectionManager.Views().map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
+ SelectionManager.Views()
+ .map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
.filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20)
- .forEach(pair => pair.doc._autoHeight = true);
+ .forEach(pair => (pair.doc._autoHeight = true));
//need to change points for resize, or else rotation/control points will fail.
- this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
+ this._inkDragDocs
+ .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
.forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
- Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
- ({
- X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
- Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
- })));
+ Doc.GetProto(doc).data = new InkField(
+ inkPts.map(
+ (
+ ipt // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
+ ) => ({
+ X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width,
+ Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height,
+ })
+ )
+ );
Doc.SetNativeWidth(doc, undefined);
Doc.SetNativeHeight(doc, undefined);
});
- }
+ };
@computed
get selectionTitle(): string {
@@ -417,58 +576,89 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
if (selected.ComponentView?.getTitle?.()) {
return selected.ComponentView.getTitle();
}
- if (this._titleControlString.startsWith("=")) {
- return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
+ if (this._titleControlString.startsWith('=')) {
+ return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || '';
}
- if (this._titleControlString.startsWith("#")) {
- return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-";
+ if (this._titleControlString.startsWith('#')) {
+ return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
}
return this._accumulatedTitle;
}
- return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-";
+ return SelectionManager.Views().length > 1 ? '-multiple-' : '-unset-';
+ }
+
+ @computed get hasIcons() {
+ return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === 'layout_icon');
}
render() {
const bounds = this.Bounds;
const seldoc = SelectionManager.Views().slice(-1)[0];
if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
- return (null);
+ return null;
}
- const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles;
- const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
- const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
- const canDelete = SelectionManager.Views().some(docView => {
- const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
- return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
- (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
- });
+ // hide the decorations if the parent chooses to hide it or if the document itself hides it
+ const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup || this._isRounding || this._isRotating;
+ const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
+ const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
+ // if multiple documents have been opened at the same time, then don't show open button
+ const hideOpenButton =
+ seldoc.props.hideOpenButton ||
+ seldoc.rootDoc.hideOpenButton ||
+ SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
+ this._isRounding ||
+ this._isRotating;
+ const hideDeleteButton =
+ this._isRounding ||
+ this._isRotating ||
+ seldoc.props.hideDeleteButton ||
+ seldoc.rootDoc.hideDeleteButton ||
+ SelectionManager.Views().some(docView => {
+ const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
+ return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
+ });
+
const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
<Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
- <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
- onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} >
+ <div
+ className={`documentDecorations-${key}Button`}
+ onContextMenu={e => e.preventDefault()}
+ onPointerDown={
+ pointerDown ??
+ (e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoBatch(e => click!(e))
+ ))
+ }>
<FontAwesomeIcon icon={icon as any} />
</div>
- </Tooltip>);
-
- const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
- const titleArea = hideTitle ? <div className="documentDecorations-title" onPointerDown={this.onTitleDown} style={{ width: "100%" }} key="title" /> :
- this._edtingTitle ?
- <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
- style={{ width: `calc(100% - ${hideResizers ? 0 : 20}px` }}
- type="text" name="dynbox" autoComplete="on"
- value={this._accumulatedTitle}
- onBlur={e => this.titleBlur()}
- onChange={action(e => this._accumulatedTitle = e.target.value)}
- onKeyPress={this.titleEntered} /> :
- <div className="documentDecorations-title" style={{ width: `calc(100% - ${hideResizers ? 0 : 20}px` }} key="title" onPointerDown={this.onTitleDown} >
- <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
- </div>;
-
- let inMainMenuPanel = false;
- for (let node = seldoc.ContentDiv; node && !inMainMenuPanel; node = node?.parentNode as any) {
- if (node.className === "mainView-mainContent") inMainMenuPanel = true;
- }
- const leftBounds = inMainMenuPanel ? 0 : this.props.boundsLeft;
+ </Tooltip>
+ );
+
+ const colorScheme = StrCast(Doc.ActiveDashboard?.colorScheme);
+ const titleArea = this._editingTitle ? (
+ <input
+ ref={this._keyinput}
+ className={`documentDecorations-title${colorScheme}`}
+ type="text"
+ name="dynbox"
+ autoComplete="on"
+ value={hideTitle ? '' : this._accumulatedTitle}
+ onBlur={e => !hideTitle && this.titleBlur()}
+ onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+ onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+ />
+ ) : (
+ <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}>
+ <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${hideTitle ? '' : this.selectionTitle}`}</span>
+ </div>
+ );
+
+ const leftBounds = this.props.boundsLeft;
const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
@@ -476,55 +666,103 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
- const useRotation = seldoc.ComponentView instanceof InkingStroke;
- const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
-
- return (<div className={`documentDecorations${colorScheme}`}>
- <div className="documentDecorations-background" style={{
- width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2,
- pointerEvents: KeyManager.Instance.ShiftPressed || this.Interacting ? "none" : "all",
- display: SelectionManager.Views().length <= 1 ? "none" : undefined
- }} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} />
- {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
- <div className="documentDecorations-container" key="container" 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,
- }}>
- {!canDelete ? <div /> : topBtn("close", "times", undefined, this.onCloseClick, "Close")}
- {titleArea}
- {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
- {hideResizers ? (null) :
- <>
- {SelectionManager.Views().length !== 1 || hideTitle ? (null) :
- topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)}
- <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
- <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
- <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
- onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown}
- onContextMenu={e => e.preventDefault()}>{useRotation && "⟲"}</div>
- </>
- }
- </div >
- {seldoc?.Document.type === DocumentType.FONTICON ? (null) : <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
- <DocumentButtonBar views={SelectionManager.Views} />
- </div>}
- </>}
- </div >
+ // Rotation constants: Only allow rotation on ink and images
+ const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox;
+ const rotation = NumCast(seldoc.rootDoc._jitterRotation);
+
+ const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
+
+ // Radius constants
+ const useRounding = seldoc.ComponentView instanceof ImageBox || seldoc.ComponentView instanceof FormattedTextBox;
+ const borderRadius = numberValue(StrCast(seldoc.rootDoc.borderRounding));
+ const docMax = Math.min(NumCast(seldoc.rootDoc.width) / 2, NumCast(seldoc.rootDoc.height) / 2);
+ const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
+ const radiusHandle = (borderRadius / docMax) * maxDist;
+ const radiusHandleLocation = Math.min(radiusHandle, maxDist);
+ return (
+ <div className={`documentDecorations${colorScheme}`}>
+ <div
+ className="documentDecorations-background"
+ style={{
+ transform: `rotate(${rotation}deg)`,
+ transformOrigin: 'top left',
+ width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
+ height: bounds.b - bounds.y + this._resizeBorderWidth + 'px',
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2,
+ pointerEvents: DocumentDecorations.Instance.AddToSelection || this.Interacting ? 'none' : 'all',
+ display: SelectionManager.Views().length <= 1 ? 'none' : undefined,
+ }}
+ onPointerDown={this.onBackgroundDown}
+ onContextMenu={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ />
+ {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? null : (
+ <div>
+ <div
+ className="documentDecorations-container"
+ key="container"
+ style={{
+ transform: `translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
+ transformOrigin: `50% calc(50% + 10px)`,
+ width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
+ height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px',
+ }}>
+ {hideDeleteButton ? <div /> : topBtn('close', this.hasIcons ? 'times' : 'window-maximize', undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), 'Close')}
+ {titleArea}
+ {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')}
+ {hideResizers ? null : (
+ <>
+ <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+ <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+ <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+ <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+ <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
+ <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+ <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+ <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+ <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
+
+ {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
+ </>
+ )}
+
+ {useRotation && (
+ <div key="rot" className={`documentDecorations-rotation`} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
+ {'⟲'}
+ </div>
+ )}
+
+ {useRounding && (
+ <div
+ key="rad"
+ style={{
+ background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`,
+ left: `${radiusHandleLocation + 3}`,
+ top: `${radiusHandleLocation + 23}`,
+ }}
+ className={`documentDecorations-borderRadius`}
+ onPointerDown={this.onRadiusDown}
+ onContextMenu={e => e.preventDefault()}
+ />
+ )}
+
+ {hideDocumentButtonBar ? null : (
+ <div
+ className="link-button-container"
+ key="links"
+ style={{
+ transform: ` translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+ }}>
+ <DocumentButtonBar views={SelectionManager.Views} />
+ </div>
+ )}
+ </div>
+ </div>
+ )}
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index d7707a6fe..8036df471 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -3,7 +3,7 @@ import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as Autosuggest from 'react-autosuggest';
import { ObjectField } from '../../fields/ObjectField';
-import "./EditableView.scss";
+import './EditableView.scss';
export interface EditableProps {
/**
@@ -26,17 +26,16 @@ export interface EditableProps {
contents: any;
fontStyle?: string;
fontSize?: number;
- height?: number | "auto";
+ height?: number | 'auto';
sizeToContent?: boolean;
maxHeight?: number;
display?: string;
overflow?: string;
autosuggestProps?: {
resetValue: () => void;
- value: string,
- onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void,
- autosuggestProps: Autosuggest.AutosuggestProps<string, any>
-
+ value: string;
+ onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void;
+ autosuggestProps: Autosuggest.AutosuggestProps<string, any>;
};
oneLine?: boolean;
editing?: boolean;
@@ -81,16 +80,16 @@ export class EditableView extends React.Component<EditableProps> {
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
switch (e.key) {
- case "Tab":
+ case 'Tab':
e.stopPropagation();
this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false);
this.props.OnTab?.(e.shiftKey);
break;
- case "Backspace":
+ case 'Backspace':
e.stopPropagation();
if (!e.currentTarget.value) this.props.OnEmpty?.();
break;
- case "Enter":
+ case 'Enter':
e.stopPropagation();
if (!e.ctrlKey) {
this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true);
@@ -100,22 +99,26 @@ export class EditableView extends React.Component<EditableProps> {
this.props.isEditingCallback?.(false);
}
break;
- case "Escape":
+ case 'Escape':
e.stopPropagation();
this._editing = false;
this.props.isEditingCallback?.(false);
break;
- case ":":
+ case ':':
this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
break;
- case "Shift": case "Alt": case "Meta": case "Control": break;
+ case 'Shift':
+ case 'Alt':
+ case 'Meta':
+ case 'Control':
+ break;
default:
if (this.props.textCallback?.(e.key)) {
this._editing = false;
- this.props.isEditingCallback?.(false,);
+ this.props.isEditingCallback?.(false);
}
}
- }
+ };
@action
onClick = (e: React.MouseEvent) => {
@@ -129,40 +132,44 @@ export class EditableView extends React.Component<EditableProps> {
}
e.stopPropagation();
}
- }
+ };
@action
finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
if (this.props.SetValue(value, shiftDown, enterKey)) {
this._editing = false;
- this.props.isEditingCallback?.(false,);
+ this.props.isEditingCallback?.(false);
} else {
this._editing = false;
this.props.isEditingCallback?.(false);
- !lostFocus && setTimeout(action(() => {
- this._editing = true;
- this.props.isEditingCallback?.(true);
- }), 0);
+ !lostFocus &&
+ setTimeout(
+ action(() => {
+ this._editing = true;
+ this.props.isEditingCallback?.(true);
+ }),
+ 0
+ );
}
}
- stopPropagation(e: React.SyntheticEvent) { e.stopPropagation(); }
+ stopPropagation(e: React.SyntheticEvent) {
+ e.stopPropagation();
+ }
@action
setIsFocused = (value: boolean) => {
const wasFocused = this._editing;
this._editing = value;
return wasFocused !== this._editing;
- }
-
-
+ };
renderEditor() {
- return this.props.autosuggestProps
- ? <Autosuggest
+ return this.props.autosuggestProps ? (
+ <Autosuggest
{...this.props.autosuggestProps.autosuggestProps}
inputProps={{
- className: "editableView-input",
+ className: 'editableView-input',
onKeyDown: this.onKeyDown,
autoFocus: true,
onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true, false),
@@ -171,39 +178,49 @@ export class EditableView extends React.Component<EditableProps> {
onPointerUp: this.stopPropagation,
onKeyPress: this.stopPropagation,
value: this.props.autosuggestProps.value,
- onChange: this.props.autosuggestProps.onChange
+ onChange: this.props.autosuggestProps.onChange,
}}
/>
- : <input className="editableView-input" ref={this._inputref}
- style={{ display: this.props.display, overflow: "auto", fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
+ ) : (
+ <input
+ className="editableView-input"
+ ref={this._inputref}
+ style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
placeholder={this.props.placeholder}
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
defaultValue={this.props.GetValue()}
autoFocus={true}
onKeyDown={this.onKeyDown}
- onKeyPress={this.stopPropagation} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
- />;
+ onKeyPress={this.stopPropagation}
+ onPointerDown={this.stopPropagation}
+ onClick={this.stopPropagation}
+ onPointerUp={this.stopPropagation}
+ />
+ );
}
render() {
if (this._editing && this.props.GetValue() !== undefined) {
- return this.props.sizeToContent ?
- <div style={{ display: "grid", minWidth: 100 }}>
- <div style={{ display: "inline-block", position: "relative", height: 0, width: "100%", overflow: "hidden" }}>
- {this.props.GetValue()}
- </div>
+ return this.props.sizeToContent ? (
+ <div style={{ display: 'grid', minWidth: 100 }}>
+ <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.props.GetValue()}</div>
{this.renderEditor()}
- </div> :
- this.renderEditor();
+ </div>
+ ) : (
+ this.renderEditor()
+ );
}
setTimeout(() => this.props.autosuggestProps?.resetValue());
- return this.props.contents instanceof ObjectField ? (null) :
- <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} ref={this._ref}
- style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: "10px", whiteSpace: "nowrap", 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 }} >
- {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
- </span>
- </div>;
+ return this.props.contents instanceof ObjectField ? null : (
+ <div
+ className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
+ ref={this._ref}
+ style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: '10px', whiteSpace: 'nowrap', height: this.props.height || 'auto', maxHeight: this.props.maxHeight }}
+ onPointerDown={e => e.stopPropagation()}
+ onClick={this.onClick}
+ placeholder={this.props.placeholder}>
+ <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 16a4c74d1..f5cbbffb1 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,11 +1,8 @@
.gestureOverlay-cont {
width: 100vw;
height: 100vh;
- position: relative;
- top: 0;
- left: 0;
+ position: absolute;
touch-action: none;
- z-index: -1;
.pointerBubbles {
width: 100%;
@@ -18,7 +15,7 @@
width: 15px;
height: 15px;
border-radius: 100%;
- border: .5px solid grey;
+ border: 0.5px solid grey;
}
}
}
@@ -44,7 +41,7 @@
width: 100%;
height: 25px;
padding: 2.5px;
- border-bottom: .5px solid black;
+ border-bottom: 0.5px solid black;
}
}
@@ -62,4 +59,4 @@
position: absolute;
background-color: transparent;
border: 1px solid black;
-} \ No newline at end of file
+}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 50dca0a99..6f28ef9eb 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,35 +1,52 @@
-import React = require("react");
+import React = require('react');
import * as fitCurve from 'fit-curve';
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
-import { InkData, InkTool } from "../../fields/InkField";
-import { Cast, FieldValue, NumCast } from "../../fields/Types";
-import MobileInkOverlay from "../../mobile/MobileInkOverlay";
-import { GestureUtils } from "../../pen-gestures/GestureUtils";
-import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { DocUtils } from "../documents/Documents";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { InteractionUtils } from "../util/InteractionUtils";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { SelectionManager } from "../util/SelectionManager";
-import { Transform } from "../util/Transform";
-import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
-import "./GestureOverlay.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveArrowScale, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
-import { DocumentView } from "./nodes/DocumentView";
-import { RadialMenu } from "./nodes/RadialMenu";
-import HorizontalPalette from "./Palette";
-import { Touchable } from "./Touchable";
-import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../fields/Doc';
+import { InkData, InkTool } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { ScriptField } from '../../fields/ScriptField';
+import { Cast, FieldValue, NumCast } from '../../fields/Types';
+import MobileInkOverlay from '../../mobile/MobileInkOverlay';
+import { GestureUtils } from '../../pen-gestures/GestureUtils';
+import { MobileInkOverlayContent } from '../../server/Message';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { CognitiveServices } from '../cognitive_services/CognitiveServices';
+import { Docs, DocUtils } from '../documents/Documents';
+import { InteractionUtils } from '../util/InteractionUtils';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SelectionManager } from '../util/SelectionManager';
+import { Transform } from '../util/Transform';
+import { CollectionFreeFormViewChrome } from './collections/CollectionMenu';
+import './GestureOverlay.scss';
+import {
+ ActiveArrowEnd,
+ ActiveArrowScale,
+ ActiveArrowStart,
+ ActiveDash,
+ ActiveFillColor,
+ ActiveInkBezierApprox,
+ ActiveInkColor,
+ ActiveInkWidth,
+ SetActiveArrowStart,
+ SetActiveDash,
+ SetActiveFillColor,
+ SetActiveInkColor,
+ SetActiveInkWidth,
+} from './InkingStroke';
+import { InkTranscription } from './InkTranscription';
+import { checkInksToGroup } from './nodes/button/FontIconBox';
+import { DocumentView } from './nodes/DocumentView';
+import { RadialMenu } from './nodes/RadialMenu';
+import HorizontalPalette from './Palette';
+import { Touchable } from './Touchable';
+import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu';
@observer
export class GestureOverlay extends Touchable {
static Instance: GestureOverlay;
- @observable public InkShape: string = "";
+ @observable public InkShape: string = '';
@observable public SavedColor?: string;
@observable public SavedWidth?: number;
@observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -40,14 +57,18 @@ export class GestureOverlay extends Touchable {
@observable private _menuX: number = -300;
@observable private _menuY: number = -300;
@observable private _pointerY?: number;
- @observable private _points: { X: number, Y: number }[] = [];
+ @observable private _points: { X: number; Y: number }[] = [];
@observable private _strokes: InkData[] = [];
@observable private _palette?: JSX.Element;
@observable private _clipboardDoc?: JSX.Element;
@observable private _possibilities: JSX.Element[] = [];
- @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100); }
- @computed private get showBounds() { return this.Tool !== ToolglassTools.None; }
+ @computed private get height(): number {
+ return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100);
+ }
+ @computed private get showBounds() {
+ return this.Tool !== ToolglassTools.None;
+ }
@observable private showMobileInkOverlay: boolean = false;
@@ -68,10 +89,75 @@ export class GestureOverlay extends Touchable {
GestureOverlay.Instance = this;
}
+ static setupThumbButtons(doc: Doc) {
+ const docProtoData: { title: string; icon: string; drag?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [
+ { title: 'use pen', icon: 'pen-nib', pointerUp: 'resetPen()', pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: 'blue' },
+ { title: 'use highlighter', icon: 'highlighter', pointerUp: 'resetPen()', pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: 'yellow' },
+ {
+ title: 'notepad',
+ icon: 'clipboard',
+ pointerUp: 'GestureOverlay.Instance.closeFloatingDoc()',
+ pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)',
+ clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }),
+ backgroundColor: 'orange',
+ },
+ { title: 'interpret text', icon: 'font', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: 'orange' },
+ { title: 'ignore gestures', icon: 'signature', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: 'green' },
+ ];
+ return docProtoData.map(data =>
+ Docs.Create.FontIconDocument({
+ _nativeWidth: 10,
+ _nativeHeight: 10,
+ _width: 10,
+ _height: 10,
+ title: data.title,
+ icon: data.icon,
+ _dropAction: data.pointerDown ? 'copy' : undefined,
+ ignoreClick: data.ignoreClick,
+ onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
+ clipboard: data.clipboard,
+ onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined,
+ onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
+ backgroundColor: data.backgroundColor,
+ _removeDropProperties: new List<string>(['dropAction']),
+ dragFactory: data.dragFactory,
+ system: true,
+ })
+ );
+ }
+
+ static setupThumbDoc(userDoc: Doc) {
+ if (!userDoc.thumbDoc) {
+ const thumbDoc = Docs.Create.LinearDocument(GestureOverlay.setupThumbButtons(userDoc), {
+ _width: 100,
+ _height: 50,
+ ignoreClick: true,
+ _lockedPosition: true,
+ title: 'buttons',
+ _autoHeight: true,
+ _yMargin: 5,
+ linearViewIsExpanded: true,
+ backgroundColor: 'white',
+ system: true,
+ });
+ thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], {
+ _width: 300,
+ _height: 25,
+ _autoHeight: true,
+ linearViewIsExpanded: true,
+ flexDirection: 'column',
+ system: true,
+ });
+ userDoc.thumbDoc = thumbDoc;
+ }
+ return Cast(userDoc.thumbDoc, Doc);
+ }
componentDidMount = () => {
- this._thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+ this._thumbDoc = FieldValue(Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc));
this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
- }
+ };
+
+ // TODO: nda - add dragging groups with one finger drag and have to click into group to scroll within the group
/**
* Ignores all touch events that belong to a hand being held down.
@@ -80,24 +166,24 @@ export class GestureOverlay extends Touchable {
const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches);
const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches);
const nt: (React.Touch | Touch)[] = Array.from(e.touches);
- this._hands.forEach((hand) => {
+ this._hands.forEach(hand => {
for (let i = 0; i < e.targetTouches.length; i++) {
const pt = e.targetTouches.item(i);
- if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+ if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
ntt.splice(ntt.indexOf(pt), 1);
}
}
for (let i = 0; i < e.changedTouches.length; i++) {
const pt = e.changedTouches.item(i);
- if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+ if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
nct.splice(nct.indexOf(pt), 1);
}
}
for (let i = 0; i < e.touches.length; i++) {
const pt = e.touches.item(i);
- if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
+ if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
nt.splice(nt.indexOf(pt), 1);
}
}
@@ -106,8 +192,8 @@ export class GestureOverlay extends Touchable {
}
onReactTouchStart = (te: React.TouchEvent) => {
- document.removeEventListener("touchmove", this.onReactHoldTouchMove);
- document.removeEventListener("touchend", this.onReactHoldTouchEnd);
+ document.removeEventListener('touchmove', this.onReactHoldTouchMove);
+ document.removeEventListener('touchend', this.onReactHoldTouchEnd);
if (RadialMenu.Instance?._display === true) {
te.preventDefault();
te.stopPropagation();
@@ -124,6 +210,8 @@ export class GestureOverlay extends Touchable {
// pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
// and this seems to be the only way of differentiating pen and touch on touch events
if (pt.radiusX > 1 && pt.radiusY > 1) {
+ InkTranscription.Instance.createInkGroup();
+ Doc.ActiveTool = InkTool.None;
this.prevPoints.set(pt.identifier, pt);
}
}
@@ -141,18 +229,16 @@ export class GestureOverlay extends Touchable {
if (nts.nt.length < 5) {
const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
target?.dispatchEvent(
- new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchStart",
- {
- bubbles: true,
- detail: {
- fingers: this.prevPoints.size,
- targetTouches: nts.ntt,
- touches: nts.nt,
- changedTouches: nts.nct,
- touchEvent: te
- }
- }
- )
+ new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>('dashOnTouchStart', {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: te,
+ },
+ })
);
if (nts.nt.length === 1) {
// -- radial menu code --
@@ -161,45 +247,41 @@ export class GestureOverlay extends Touchable {
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",
- {
- bubbles: true,
- detail: {
- fingers: this.prevPoints.size,
- targetTouches: nts.ntt,
- touches: nts.nt,
- changedTouches: nts.nct,
- touchEvent: te
- }
- }
- )
+ new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>('dashOnTouchHoldStart', {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: te,
+ },
+ })
);
this._holdTimer = undefined;
- document.removeEventListener("touchmove", this.onReactTouchMove);
- document.removeEventListener("touchend", this.onReactTouchEnd);
- document.removeEventListener("touchmove", this.onReactHoldTouchMove);
- document.removeEventListener("touchend", this.onReactHoldTouchEnd);
- document.addEventListener("touchmove", this.onReactHoldTouchMove);
- document.addEventListener("touchend", this.onReactHoldTouchEnd);
+ document.removeEventListener('touchmove', this.onReactTouchMove);
+ document.removeEventListener('touchend', this.onReactTouchEnd);
+ document.removeEventListener('touchmove', this.onReactHoldTouchMove);
+ document.removeEventListener('touchend', this.onReactHoldTouchEnd);
+ document.addEventListener('touchmove', this.onReactHoldTouchMove);
+ document.addEventListener('touchend', this.onReactHoldTouchEnd);
}
-
- }, (500));
- }
- else {
+ }, 500);
+ } else {
this._holdTimer && clearTimeout(this._holdTimer);
}
- document.removeEventListener("touchmove", this.onReactTouchMove);
- document.removeEventListener("touchend", this.onReactTouchEnd);
- document.addEventListener("touchmove", this.onReactTouchMove);
- document.addEventListener("touchend", this.onReactTouchEnd);
+ document.removeEventListener('touchmove', this.onReactTouchMove);
+ document.removeEventListener('touchend', this.onReactTouchEnd);
+ document.addEventListener('touchmove', this.onReactTouchMove);
+ document.addEventListener('touchend', this.onReactTouchEnd);
}
// otherwise, handle as a hand event
else {
this.handleHandDown(te);
- document.removeEventListener("touchmove", this.onReactTouchMove);
- document.removeEventListener("touchend", this.onReactTouchEnd);
+ document.removeEventListener('touchmove', this.onReactTouchMove);
+ document.removeEventListener('touchend', this.onReactTouchEnd);
}
- }
+ };
onReactTouchMove = (e: TouchEvent) => {
const nts: any = this.getNewTouches(e);
@@ -207,19 +289,18 @@ export class GestureOverlay extends Touchable {
this._holdTimer = undefined;
document.dispatchEvent(
- new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchMove",
- {
- bubbles: true,
- detail: {
- fingers: this.prevPoints.size,
- targetTouches: nts.ntt,
- touches: nts.nt,
- changedTouches: nts.nct,
- touchEvent: e
- }
- })
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchMove', {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: e,
+ },
+ })
);
- }
+ };
onReactTouchEnd = (e: TouchEvent) => {
const nts: any = this.getNewTouches(e);
@@ -227,17 +308,16 @@ export class GestureOverlay extends Touchable {
this._holdTimer = undefined;
document.dispatchEvent(
- new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchEnd",
- {
- bubbles: true,
- detail: {
- fingers: this.prevPoints.size,
- targetTouches: nts.ntt,
- touches: nts.nt,
- changedTouches: nts.nct,
- touchEvent: e
- }
- })
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchEnd', {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: e,
+ },
+ })
);
// cleanup any lingering pointers
@@ -251,11 +331,11 @@ export class GestureOverlay extends Touchable {
}
if (this.prevPoints.size === 0) {
- document.removeEventListener("touchmove", this.onReactTouchMove);
- document.removeEventListener("touchend", this.onReactTouchEnd);
+ document.removeEventListener('touchmove', this.onReactTouchMove);
+ document.removeEventListener('touchend', this.onReactTouchEnd);
}
e.stopPropagation();
- }
+ };
handleHandDown = async (e: React.TouchEvent) => {
this._holdTimer && clearTimeout(this._holdTimer);
@@ -279,20 +359,17 @@ export class GestureOverlay extends Touchable {
}
// this chunk of code determines whether this is a left hand or a right hand, as well as which pointer is the thumb and pointer
- const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+ const thumb = fingers.reduce((a, v) => (a.clientY > v.clientY ? a : v), fingers[0]);
const rightMost = Math.max(...fingers.map(f => f.clientX));
const leftMost = Math.min(...fingers.map(f => f.clientX));
let pointer: React.Touch | undefined;
// left hand
if (thumb.clientX === rightMost) {
- pointer = fingers.reduce((a, v) => a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v);
+ pointer = fingers.reduce((a, v) => (a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v));
}
// right hand
else if (thumb.clientX === leftMost) {
- pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v);
- }
- else {
- console.log("not hand");
+ pointer = fingers.reduce((a, v) => (a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v));
}
this.pointerIdentifier = pointer?.identifier;
@@ -313,7 +390,7 @@ export class GestureOverlay extends Touchable {
const minY = Math.min(...others.map(f => f.clientY));
// load up the palette collection around the thumb
- const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc);
+ const thumbDoc = await Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc);
if (thumbDoc) {
runInAction(() => {
RadialMenu.Instance._display = false;
@@ -328,11 +405,11 @@ export class GestureOverlay extends Touchable {
}
this.removeMoveListeners();
- document.removeEventListener("touchmove", this.handleHandMove);
- document.addEventListener("touchmove", this.handleHandMove);
- document.removeEventListener("touchend", this.handleHandUp);
- document.addEventListener("touchend", this.handleHandUp);
- }
+ document.removeEventListener('touchmove', this.handleHandMove);
+ document.addEventListener('touchmove', this.handleHandMove);
+ document.removeEventListener('touchend', this.handleHandUp);
+ document.addEventListener('touchend', this.handleHandUp);
+ };
@action
handleHandMove = (e: TouchEvent) => {
@@ -345,18 +422,20 @@ export class GestureOverlay extends Touchable {
const tPt = e.targetTouches.item(j);
if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
if (pt && this.prevPoints.has(pt.identifier)) {
- this._hands.forEach(hand => hand.some(f => {
- if (f.identifier === pt.identifier) {
- fingers.push(pt);
- }
- }));
+ this._hands.forEach(hand =>
+ hand.some(f => {
+ if (f.identifier === pt.identifier) {
+ fingers.push(pt);
+ }
+ })
+ );
}
}
}
}
}
// update hand trackers
- const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+ const thumb = fingers.reduce((a, v) => (a.clientY > v.clientY ? a : v), fingers[0]);
if (thumb?.identifier && thumb?.identifier === this.thumbIdentifier) {
this._hands.set(thumb.identifier, fingers);
}
@@ -370,12 +449,11 @@ export class GestureOverlay extends Touchable {
// moving a thumb horiz. changes the palette collection selection, moving vert. changes the selection of any menus on the current palette item
const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY);
if ((yOverX && this._inkToTextDoc) || this._selectedIndex > -1) {
- if (Math.abs(pt.clientY - this._thumbY) > (10 * window.devicePixelRatio)) {
- this._selectedIndex = Math.min(Math.max(-1, (-Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1)), this._possibilities.length - 1);
+ if (Math.abs(pt.clientY - this._thumbY) > 10 * window.devicePixelRatio) {
+ this._selectedIndex = Math.min(Math.max(-1, -Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1), this._possibilities.length - 1);
}
- }
- else if (this._thumbDoc) {
- if (Math.abs(pt.clientX - this._thumbX) > (15 * window.devicePixelRatio)) {
+ } else if (this._thumbDoc) {
+ if (Math.abs(pt.clientX - this._thumbX) > 15 * window.devicePixelRatio) {
this._thumbDoc.selectedIndex = Math.max(-1, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX));
this._thumbX = pt.clientX;
}
@@ -387,7 +465,7 @@ export class GestureOverlay extends Touchable {
this._pointerY = pt.clientY;
}
}
- }
+ };
@action
handleHandUp = (e: TouchEvent) => {
@@ -419,38 +497,37 @@ export class GestureOverlay extends Touchable {
this._strokes = [];
this._points = [];
this._possibilities = [];
- document.removeEventListener("touchend", this.handleHandUp);
+ document.removeEventListener('touchend', this.handleHandUp);
}
- }
+ };
/**
* Code for radial menu
*/
onReactHoldTouchMove = (e: TouchEvent) => {
- document.removeEventListener("touchmove", this.onReactTouchMove);
- document.removeEventListener("touchend", this.onReactTouchEnd);
- document.removeEventListener("touchmove", this.onReactHoldTouchMove);
- document.removeEventListener("touchend", this.onReactHoldTouchEnd);
- document.addEventListener("touchmove", this.onReactHoldTouchMove);
- document.addEventListener("touchend", this.onReactHoldTouchEnd);
+ document.removeEventListener('touchmove', this.onReactTouchMove);
+ document.removeEventListener('touchend', this.onReactTouchEnd);
+ document.removeEventListener('touchmove', this.onReactHoldTouchMove);
+ document.removeEventListener('touchend', this.onReactHoldTouchEnd);
+ document.addEventListener('touchmove', this.onReactHoldTouchMove);
+ document.addEventListener('touchend', this.onReactHoldTouchEnd);
const nts: any = this.getNewTouches(e);
if (this.prevPoints.size === 1 && this._holdTimer) {
clearTimeout(this._holdTimer);
}
document.dispatchEvent(
- new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldMove",
- {
- bubbles: true,
- detail: {
- fingers: this.prevPoints.size,
- targetTouches: nts.ntt,
- touches: nts.nt,
- changedTouches: nts.nct,
- touchEvent: e
- }
- })
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchHoldMove', {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: e,
+ },
+ })
);
- }
+ };
/**
* Code for radial menu
@@ -462,17 +539,16 @@ export class GestureOverlay extends Touchable {
this._holdTimer = undefined;
}
document.dispatchEvent(
- new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchHoldEnd",
- {
- bubbles: true,
- detail: {
- fingers: this.prevPoints.size,
- targetTouches: nts.ntt,
- touches: nts.nt,
- changedTouches: nts.nct,
- touchEvent: e
- }
- })
+ new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchHoldEnd', {
+ bubbles: true,
+ detail: {
+ fingers: this.prevPoints.size,
+ targetTouches: nts.ntt,
+ touches: nts.nt,
+ changedTouches: nts.nct,
+ touchEvent: e,
+ },
+ })
);
for (let i = 0; i < e.changedTouches.length; i++) {
const pt = e.changedTouches.item(i);
@@ -483,20 +559,40 @@ export class GestureOverlay extends Touchable {
}
}
- document.removeEventListener("touchmove", this.onReactHoldTouchMove);
- document.removeEventListener("touchend", this.onReactHoldTouchEnd);
+ document.removeEventListener('touchmove', this.onReactHoldTouchMove);
+ document.removeEventListener('touchend', this.onReactHoldTouchEnd);
e.stopPropagation();
- }
+ };
@action
onPointerDown = (e: React.PointerEvent) => {
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
- this._points.push({ X: e.clientX, Y: e.clientY });
- setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
- // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action((e: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap) {
+ InkTranscription.Instance.createInkGroup();
+ Doc.ActiveTool = InkTool.None;
+ return;
+ }
+ })
+ );
}
- }
+ if (!(e.target as any)?.className?.toString().startsWith('lm_')) {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ Doc.ActiveTool = InkTool.Write;
+ }
+ this._points.push({ X: e.clientX, Y: e.clientY });
+ setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
+ // if (Doc.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+ }
+ }
+ };
@action
onPointerMove = (e: PointerEvent) => {
@@ -504,17 +600,18 @@ export class GestureOverlay extends Touchable {
if (this._points.length > 1) {
const B = this.svgBounds;
- const initialPoint = this._points[0.];
+ 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;
const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
switch (this.Tool) {
- case ToolglassTools.RadialMenu: return true;
+ case ToolglassTools.RadialMenu:
+ return true;
}
}
}
return false;
- }
+ };
handleLineGesture = (): boolean => {
const actionPerformed = false;
@@ -526,19 +623,18 @@ export class GestureOverlay extends Touchable {
const target1 = document.elementFromPoint(ep1.X, ep1.Y);
const target2 = document.elementFromPoint(ep2.X, ep2.Y);
- const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
- {
- bubbles: true,
- detail: {
- points: this._points,
- gesture: GestureUtils.Gestures.Line,
- bounds: B
- }
- });
+ const ge = new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
+ bubbles: true,
+ detail: {
+ points: this._points,
+ gesture: GestureUtils.Gestures.Line,
+ bounds: B,
+ },
+ });
target1?.dispatchEvent(ge);
target2?.dispatchEvent(ge);
return actionPerformed;
- }
+ };
@action
onPointerUp = (e: PointerEvent) => {
@@ -548,9 +644,9 @@ export class GestureOverlay extends Touchable {
//push first points to so interactionUtil knows pointer is up
this._points.push({ X: this._points[0].X, Y: this._points[0].Y });
- 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);
- const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+ 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;
+ const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
// if a toolglass is selected and the stroke starts within the toolglass boundaries
if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
@@ -558,8 +654,8 @@ export class GestureOverlay extends Touchable {
case ToolglassTools.InkToText:
this._strokes.push(new Array(...this._points));
this._points = [];
- CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => {
- const wordResults = results.filter((r: any) => r.category === "line");
+ CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then(results => {
+ const wordResults = results.filter((r: any) => r.category === 'line');
const possibilities: string[] = [];
for (const wR of wordResults) {
if (wR?.recognizedText) {
@@ -573,8 +669,7 @@ export class GestureOverlay extends Touchable {
// if we receive any word results from cognitive services, display them
runInAction(() => {
- this._possibilities = possibilities.map(p =>
- <TouchScrollableMenuItem text={p} onClick={() => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />);
+ this._possibilities = possibilities.map(p => <TouchScrollableMenuItem text={p} onClick={() => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />);
});
});
break;
@@ -590,49 +685,68 @@ export class GestureOverlay extends Touchable {
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
- this.InkShape = "";
- Doc.UserDoc().activeInkTool = InkTool.None;
+ this.InkShape = '';
+ Doc.ActiveTool = InkTool.None;
}
}
// if we're not drawing in a toolglass try to recognize as gesture
- else { // need to decide when to turn gestures back on
+ else {
+ // need to decide when to turn gestures back on
const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points));
let actionPerformed = false;
if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
switch (result.Name) {
- case GestureUtils.Gestures.Box: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box); break;
- case GestureUtils.Gestures.StartBracket: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket); break;
- case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture("endbracket"); break;
- case GestureUtils.Gestures.Line: actionPerformed = this.handleLineGesture(); break;
- case GestureUtils.Gestures.Triangle: actionPerformed = this.makePolygon("triangle", true); break;
- case GestureUtils.Gestures.Circle: actionPerformed = this.makePolygon("circle", true); break;
- case GestureUtils.Gestures.Rectangle: actionPerformed = this.makePolygon("rectangle", true); break;
- case GestureUtils.Gestures.Scribble: console.log("scribble"); break;
+ case GestureUtils.Gestures.Box:
+ actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box);
+ break;
+ case GestureUtils.Gestures.StartBracket:
+ actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket);
+ break;
+ case GestureUtils.Gestures.EndBracket:
+ actionPerformed = this.dispatchGesture('endbracket');
+ break;
+ case GestureUtils.Gestures.Line:
+ actionPerformed = this.handleLineGesture();
+ break;
+ case GestureUtils.Gestures.Triangle:
+ actionPerformed = this.makePolygon('triangle', true);
+ break;
+ case GestureUtils.Gestures.Circle:
+ actionPerformed = this.makePolygon('circle', true);
+ break;
+ case GestureUtils.Gestures.Rectangle:
+ actionPerformed = this.makePolygon('rectangle', true);
+ break;
+ case GestureUtils.Gestures.Scribble:
+ console.log('scribble');
+ break;
}
}
// if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
if (!actionPerformed) {
- const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
+ const newPoints = this._points.reduce((p, pts) => {
+ p.push([pts.X, pts.Y]);
+ return p;
+ }, [] as number[][]);
newPoints.pop();
- const controlPoints: { X: number, Y: number }[] = [];
+ const controlPoints: { X: number; Y: number }[] = [];
const bezierCurves = fitCurve(newPoints, 10);
for (const curve of bezierCurves) {
-
controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
-
-
}
- const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) +
- (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y));
+ const dist = Math.sqrt(
+ (controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)
+ );
if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
this._points = controlPoints;
-
this.dispatchGesture(GestureUtils.Gestures.Stroke);
+ // TODO: nda - check inks to group here
+ checkInksToGroup();
}
this._points = [];
}
@@ -640,7 +754,7 @@ export class GestureOverlay extends Touchable {
this._points = [];
}
CollectionFreeFormViewChrome.Instance?.primCreated();
- }
+ };
makePolygon = (shape: string, gesture: boolean) => {
//take off gesture recognition for now
@@ -658,11 +772,15 @@ export class GestureOverlay extends Touchable {
var lastx = this._points[this._points.length - 2].X;
var lasty = this._points[this._points.length - 2].Y;
var fourth = (lastx - firstx) / 4;
- if (isNaN(fourth) || fourth === 0) { fourth = 0.01; }
+ if (isNaN(fourth) || fourth === 0) {
+ fourth = 0.01;
+ }
var m = (lasty - firsty) / (lastx - firstx);
- if (isNaN(m) || m === 0) { m = 0.01; }
+ if (isNaN(m) || m === 0) {
+ m = 0.01;
+ }
const b = firsty - m * firstx;
- if (shape === "noRec") {
+ if (shape === 'noRec') {
return false;
}
if (!gesture) {
@@ -672,7 +790,7 @@ export class GestureOverlay extends Touchable {
left = this._points[0].X;
bottom = this._points[this._points.length - 2].Y;
top = this._points[0].Y;
- if (shape !== "arrow" && shape !== "line" && shape !== "circle") {
+ if (shape !== 'arrow' && shape !== 'line' && shape !== 'circle') {
if (left > right) {
const temp = right;
right = left;
@@ -689,7 +807,7 @@ export class GestureOverlay extends Touchable {
switch (shape) {
//must push an extra point in the end so InteractionUtils knows pointer is up.
//must be (points[0].X,points[0]-1)
- case "rectangle":
+ case 'rectangle':
this._points.push({ X: left, Y: top });
this._points.push({ X: left, Y: top });
this._points.push({ X: right, Y: top });
@@ -712,7 +830,7 @@ export class GestureOverlay extends Touchable {
break;
- case "triangle":
+ case 'triangle':
this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
@@ -729,9 +847,8 @@ export class GestureOverlay extends Touchable {
this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
-
break;
- case "circle":
+ case 'circle':
// Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
// making the curves indistinguishable from a circle.
// Source: https://spencermortensen.com/articles/bezier-circle/
@@ -740,30 +857,30 @@ export class GestureOverlay extends Touchable {
const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
- // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
+ // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
this._points.push({ X: centerX, Y: centerY + radius });
- this._points.push({ X: centerX + (c * radius), Y: centerY + radius });
- this._points.push({ X: centerX + radius, Y: centerY + (c * radius) });
+ this._points.push({ X: centerX + c * radius, Y: centerY + radius });
+ this._points.push({ X: centerX + radius, Y: centerY + c * radius });
this._points.push({ X: centerX + radius, Y: centerY });
this._points.push({ X: centerX + radius, Y: centerY });
- this._points.push({ X: centerX + radius, Y: centerY - (c * radius) });
- this._points.push({ X: centerX + (c * radius), Y: centerY - radius });
+ this._points.push({ X: centerX + radius, Y: centerY - c * radius });
+ this._points.push({ X: centerX + c * radius, Y: centerY - radius });
this._points.push({ X: centerX, Y: centerY - radius });
this._points.push({ X: centerX, Y: centerY - radius });
- this._points.push({ X: centerX - (c * radius), Y: centerY - radius });
- this._points.push({ X: centerX - radius, Y: centerY - (c * radius) });
+ this._points.push({ X: centerX - c * radius, Y: centerY - radius });
+ this._points.push({ X: centerX - radius, Y: centerY - c * radius });
this._points.push({ X: centerX - radius, Y: centerY });
this._points.push({ X: centerX - radius, Y: centerY });
- this._points.push({ X: centerX - radius, Y: centerY + (c * radius) });
- this._points.push({ X: centerX - (c * radius), Y: centerY + radius });
+ this._points.push({ X: centerX - radius, Y: centerY + c * radius });
+ this._points.push({ X: centerX - c * radius, Y: centerY + radius });
this._points.push({ X: centerX, Y: centerY + radius });
break;
- case "line":
+ case 'line':
if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) {
lastx = firstx;
}
@@ -776,12 +893,12 @@ export class GestureOverlay extends Touchable {
this._points.push({ X: lastx, Y: lasty });
this._points.push({ X: lastx, Y: lasty });
break;
- case "arrow":
+ case 'arrow':
const x1 = left;
const y1 = top;
const x2 = right;
const y2 = bottom;
- const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + (Math.pow(Math.abs(y1 - y2), 2)));
+ const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2));
const L2 = L1 / 5;
const angle = 0.785398;
const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
@@ -796,24 +913,24 @@ export class GestureOverlay extends Touchable {
// this._points.push({ X: x1, Y: y1 - 1 });
}
return true;
- }
+ };
- dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => {
+ dispatchGesture = (gesture: 'box' | 'line' | 'startbracket' | 'endbracket' | 'stroke' | 'scribble' | 'text', stroke?: InkData, data?: any) => {
const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
- return target?.dispatchEvent(
- new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
- {
+ return (
+ target?.dispatchEvent(
+ new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
bubbles: true,
detail: {
points: stroke ?? this._points,
gesture: gesture as any,
bounds: this.getBounds(stroke ?? this._points),
- text: data
- }
- }
- )
- ) || false;
- }
+ text: data,
+ },
+ })
+ ) || false
+ );
+ };
getBounds = (stroke: InkData, pad?: boolean) => {
const padding = pad ? [-20000, 20000] : [];
@@ -824,7 +941,7 @@ export class GestureOverlay extends Touchable {
const bottom = Math.max(...ys);
const top = Math.min(...ys);
return { right, left, bottom, top, width: right - left, height: bottom - top };
- }
+ };
@computed get svgBounds() {
return this.getBounds(this._points);
@@ -832,7 +949,7 @@ export class GestureOverlay extends Touchable {
@computed get elements() {
const selView = SelectionManager.Views().lastElement();
- const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc.viewScale, 1) / (selView?.props.ScreenToLocalTransform().Scale || 1);
+ const width = (Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1)) / (selView?.props.ScreenToLocalTransform().Scale || 1);
const rect = this._overlayRef.current?.getBoundingClientRect();
const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
B.left = B.left - width / 2;
@@ -842,30 +959,74 @@ export class GestureOverlay extends Touchable {
B.width += width;
B.height += width;
const fillColor = ActiveFillColor();
- const strokeColor = fillColor && fillColor !== "transparent" ? fillColor : ActiveInkColor();
+ const strokeColor = fillColor && fillColor !== 'transparent' ? fillColor : ActiveInkColor();
return [
this.props.children,
this._palette,
- [this._strokes.map((l, i) => {
- const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 };//this.getBounds(l, true);
- return <svg key={i} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(l, b.left, b.top, strokeColor, width, width, "miter", "round",
- ActiveInkBezierApprox(), "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveArrowScale(),
- ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
- </svg>;
- }),
- this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
- style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "miter", "round", "",
- "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveArrowScale(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)}
- </svg>]
+ [
+ this._strokes.map((l, i) => {
+ const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(l, true);
+ return (
+ <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
+ {InteractionUtils.CreatePolyline(
+ l,
+ b.left,
+ b.top,
+ strokeColor,
+ width,
+ width,
+ 'miter',
+ 'round',
+ ActiveInkBezierApprox(),
+ 'none' /*ActiveFillColor()*/,
+ ActiveArrowStart(),
+ ActiveArrowEnd(),
+ ActiveArrowScale(),
+ ActiveDash(),
+ 1,
+ 1,
+ this.InkShape,
+ 'none',
+ 1.0,
+ false
+ )}
+ </svg>
+ );
+ }),
+ this._points.length <= 1 ? null : (
+ <svg key="svg" width={B.width} height={B.height} style={{ top: 0, left: 0, transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
+ {InteractionUtils.CreatePolyline(
+ this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })),
+ B.left,
+ B.top,
+ ActiveInkColor(),
+ width,
+ width,
+ 'miter',
+ 'round',
+ '',
+ 'none' /*ActiveFillColor()*/,
+ ActiveArrowStart(),
+ ActiveArrowEnd(),
+ ActiveArrowScale(),
+ ActiveDash(),
+ 1,
+ 1,
+ this.InkShape,
+ 'none',
+ 1.0,
+ false
+ )}
+ </svg>
+ ),
+ ],
];
}
screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1);
return300 = () => 300;
@action
public openFloatingDoc = (doc: Doc) => {
- this._clipboardDoc =
+ this._clipboardDoc = (
<DocumentView
Document={doc}
DataDoc={undefined}
@@ -881,7 +1042,6 @@ export class GestureOverlay extends Touchable {
isContentActive={returnFalse}
renderDepth={0}
styleProvider={returnEmptyString}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
focus={DocUtils.DefaultFocus}
whenChildContentsActiveChanged={emptyFunction}
@@ -891,61 +1051,65 @@ export class GestureOverlay extends Touchable {
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- />;
- }
+ />
+ );
+ };
@action
public closeFloatingDoc = () => {
this._clipboardDoc = undefined;
- }
+ };
@action
enableMobileInkOverlay = (content: MobileInkOverlayContent) => {
this.showMobileInkOverlay = content.enableOverlay;
- }
+ };
render() {
return (
- <div className="gestureOverlay-cont" ref={this._overlayRef}
- onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+ <div className="gestureOverlay-cont" ref={this._overlayRef} onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
{this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
{this.elements}
- <div className="clipboardDoc-cont" style={{
- height: this.height,
- width: this.height,
- pointerEvents: this._clipboardDoc ? "unset" : "none",
- touchAction: this._clipboardDoc ? "unset" : "none",
- transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height} px)`,
- }}>
+ <div
+ className="clipboardDoc-cont"
+ style={{
+ height: this.height,
+ width: this.height,
+ pointerEvents: this._clipboardDoc ? 'unset' : 'none',
+ touchAction: this._clipboardDoc ? 'unset' : 'none',
+ transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height} px)`,
+ }}>
{this._clipboardDoc}
</div>
- <div className="filter-cont" style={{
- transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height}px)`,
- height: this.height,
- width: this.height,
- pointerEvents: "none",
- touchAction: "none",
- display: this.showBounds ? "unset" : "none",
- }}>
- </div>
+ <div
+ className="filter-cont"
+ style={{
+ transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height}px)`,
+ height: this.height,
+ width: this.height,
+ pointerEvents: 'none',
+ touchAction: 'none',
+ display: this.showBounds ? 'unset' : 'none',
+ }}></div>
<TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
- </div>);
+ </div>
+ );
}
}
// export class
export enum ToolglassTools {
- InkToText = "inktotext",
- IgnoreGesture = "ignoregesture",
- RadialMenu = "radialmenu",
- None = "none",
+ InkToText = 'inktotext',
+ IgnoreGesture = 'ignoregesture',
+ RadialMenu = 'radialmenu',
+ None = 'none',
}
-ScriptingGlobals.add("GestureOverlay", GestureOverlay);
+ScriptingGlobals.add('GestureOverlay', GestureOverlay);
ScriptingGlobals.add(function setToolglass(tool: any) {
- runInAction(() => GestureOverlay.Instance.Tool = tool);
+ runInAction(() => (GestureOverlay.Instance.Tool = tool));
});
ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
runInAction(() => {
@@ -961,10 +1125,14 @@ ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowSta
});
ScriptingGlobals.add(function resetPen() {
runInAction(() => {
- SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
- SetActiveInkWidth(GestureOverlay.Instance.SavedWidth?.toString() ?? "2");
+ SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)');
+ SetActiveInkWidth(GestureOverlay.Instance.SavedWidth?.toString() ?? '2');
});
-}, "resets the pen tool");
-ScriptingGlobals.add(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)");
+}, 'resets the pen tool');
+ScriptingGlobals.add(
+ 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)'
+);
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 1a4080d81..85f579975 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,64 +1,62 @@
-import { random } from "lodash";
-import { action, observable } from "mobx";
-import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { InkTool } from "../../fields/InkField";
-import { List } from "../../fields/List";
-import { ScriptField } from "../../fields/ScriptField";
-import { Cast, PromiseValue } from "../../fields/Types";
-import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
-import { DocServer } from "../DocServer";
-import { DocumentType } from "../documents/DocumentTypes";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { DragManager } from "../util/DragManager";
-import { GroupManager } from "../util/GroupManager";
-import { SelectionManager } from "../util/SelectionManager";
-import { SettingsManager } from "../util/SettingsManager";
-import { SharingManager } from "../util/SharingManager";
-import { SnappingManager } from "../util/SnappingManager";
-import { undoBatch, UndoManager } from "../util/UndoManager";
-import { CollectionDockingView } from "./collections/CollectionDockingView";
-import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
-import { CollectionStackedTimeline } from "./collections/CollectionStackedTimeline";
-import { ContextMenu } from "./ContextMenu";
-import { DocumentDecorations } from "./DocumentDecorations";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { LightboxView } from "./LightboxView";
-import { MainView } from "./MainView";
-import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
-import { AnchorMenu } from "./pdf/AnchorMenu";
+import { random } from 'lodash';
+import { action, observable, runInAction } from 'mobx';
+import { DateField } from '../../fields/DateField';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { InkTool } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { ScriptField } from '../../fields/ScriptField';
+import { Cast, PromiseValue } from '../../fields/Types';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
+import { DocServer } from '../DocServer';
+import { DocumentType } from '../documents/DocumentTypes';
+import { DragManager } from '../util/DragManager';
+import { GroupManager } from '../util/GroupManager';
+import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
+import { SharingManager } from '../util/SharingManager';
+import { SnappingManager } from '../util/SnappingManager';
+import { undoBatch, UndoManager } from '../util/UndoManager';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionFreeFormViewChrome } from './collections/CollectionMenu';
+import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
+import { ContextMenu } from './ContextMenu';
+import { DocumentDecorations } from './DocumentDecorations';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { LightboxView } from './LightboxView';
+import { MainView } from './MainView';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { AnchorMenu } from './pdf/AnchorMenu';
-const modifiers = ["control", "meta", "shift", "alt"];
-type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
+const modifiers = ['control', 'meta', 'shift', 'alt'];
+type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
type KeyControlInfo = {
- preventDefault: boolean,
- stopPropagation: boolean
+ preventDefault: boolean;
+ stopPropagation: boolean;
};
export class KeyManager {
public static Instance: KeyManager = new KeyManager();
private router = new Map<string, KeyHandler>();
- @observable ShiftPressed = false;
constructor() {
- const isMac = navigator.platform.toLowerCase().indexOf("mac") >= 0;
+ const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
// SHIFT CONTROL ALT META
- this.router.set("0000", this.unmodified);
- this.router.set(isMac ? "0001" : "0100", this.ctrl);
- this.router.set(isMac ? "0100" : "0010", this.alt);
- this.router.set(isMac ? "1001" : "1100", this.ctrl_shift);
- this.router.set("1000", this.shift);
+ this.router.set('0000', this.unmodified);
+ this.router.set(isMac ? '0001' : '0100', this.ctrl);
+ this.router.set(isMac ? '0100' : '0010', this.alt);
+ this.router.set(isMac ? '1001' : '1100', this.ctrl_shift);
+ this.router.set('1000', this.shift);
}
public unhandle = action((e: KeyboardEvent) => {
- if (e.key?.toLowerCase() === "shift") KeyManager.Instance.ShiftPressed = false;
+ if (e.key?.toLowerCase() === 'shift') runInAction(() => (DocumentDecorations.Instance.AddToSelection = false));
});
- public handle = action(async (e: KeyboardEvent) => {
- if (e.key?.toLowerCase() === "shift" && e.ctrlKey && e.altKey) KeyManager.Instance.ShiftPressed = true;
- //if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
+ public handle = action((e: KeyboardEvent) => {
+ if (e.key?.toLowerCase() === 'shift') DocumentDecorations.Instance.AddToSelection = true;
+ //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
const keyname = e.key && e.key.toLowerCase();
this.handleGreedy(keyname);
@@ -66,7 +64,7 @@ export class KeyManager {
return;
}
- const bit = (value: boolean) => value ? "1" : "0";
+ const bit = (value: boolean) => (value ? '1' : '0');
const modifierIndex = bit(e.shiftKey) + bit(e.ctrlKey) + bit(e.altKey) + bit(e.metaKey);
const handleConstrained = this.router.get(modifierIndex);
@@ -74,7 +72,7 @@ export class KeyManager {
return;
}
- const control = await handleConstrained(keyname, e);
+ const control = handleConstrained(keyname, e);
control.stopPropagation && e.stopPropagation();
control.preventDefault && e.preventDefault();
@@ -87,38 +85,39 @@ export class KeyManager {
private unmodified = action((keyname: string, e: KeyboardEvent) => {
switch (keyname) {
- case "u":
- if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ case 'u':
+ if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
return { stopPropagation: false, preventDefault: false };
}
const ungroupings = SelectionManager.Views().slice();
- UndoManager.RunInBatch(() => ungroupings.map(dv => dv.layoutDoc.group = undefined), "ungroup");
+ UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup');
SelectionManager.DeselectAll();
break;
- case "g":
- if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ case 'g':
+ if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
return { stopPropagation: false, preventDefault: false };
}
const groupings = SelectionManager.Views().slice();
const randomGroup = random(0, 1000);
- UndoManager.RunInBatch(() => groupings.map(dv => dv.layoutDoc.group = randomGroup), "group");
+ UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group');
SelectionManager.DeselectAll();
break;
- case " ":
+ case ' ':
// MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
- case "escape":
+ case 'escape':
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
InkStrokeProperties.Instance._controlButton = false;
- CurrentUserUtils.SelectedTool = InkTool.None;
+ Doc.ActiveTool = InkTool.None;
+ DragManager.CompleteWindowDrag?.(true);
var doDeselect = true;
if (SnappingManager.GetIsDragging()) {
DragManager.AbortDrag();
- } else if (CollectionDockingView.Instance.HasFullScreen) {
- CollectionDockingView.Instance.CloseFullScreen();
+ } else if (CollectionDockingView.Instance?.HasFullScreen) {
+ CollectionDockingView.Instance?.CloseFullScreen();
} else if (CollectionStackedTimeline.SelectingRegion) {
CollectionStackedTimeline.SelectingRegion = undefined;
doDeselect = false;
@@ -138,43 +137,69 @@ export class KeyManager {
window.getSelection()?.empty();
document.body.focus();
break;
- case "delete":
- case "backspace":
- if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") {
- const selected = SelectionManager.Views().filter(dv => !dv.topMost);
+ case 'enter': {
+ DocumentDecorations.Instance.onCloseClick(false);
+ break;
+ }
+ case 'delete':
+ case 'backspace':
+ if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
UndoManager.RunInBatch(() => {
- SelectionManager.DeselectAll();
- selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document));
- }, "delete");
+ if (LightboxView.LightboxDoc) {
+ LightboxView.SetLightboxDoc(undefined);
+ SelectionManager.DeselectAll();
+ } else DocumentDecorations.Instance.onCloseClick(true);
+ }, 'backspace');
+ // const selected = SelectionManager.Views().filter(dv => !dv.topMost);
+ // UndoManager.RunInBatch(() => {
+ // SelectionManager.DeselectAll();
+ // selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document));
+ // }, "delete");
return { stopPropagation: true, preventDefault: true };
}
break;
- case "arrowleft": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge(-1, 0)), "nudge left"); break;
- case "arrowright": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(1, 0)), "nudge right"); break;
- case "arrowup": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -1)), "nudge up"); break;
- case "arrowdown": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 1)), "nudge down"); break;
+ case 'arrowleft':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge(-1, 0)), 'nudge left');
+ break;
+ case 'arrowright':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(1, 0)), 'nudge right');
+ break;
+ case 'arrowup':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -1)), 'nudge up');
+ break;
+ case 'arrowdown':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 1)), 'nudge down');
+ break;
}
return {
stopPropagation: false,
- preventDefault: false
+ preventDefault: false,
};
});
- private shift = action(async (keyname: string) => {
+ private shift = action((keyname: string) => {
const stopPropagation = false;
const preventDefault = false;
switch (keyname) {
- case "arrowleft": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(-10, 0)), "nudge left"); break;
- case "arrowright": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(10, 0)), "nudge right"); break;
- case "arrowup": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -10)), "nudge up"); break;
- case "arrowdown": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), "nudge down"); break;
+ case 'arrowleft':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(-10, 0)), 'nudge left');
+ break;
+ case 'arrowright':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(10, 0)), 'nudge right');
+ break;
+ case 'arrowup':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -10)), 'nudge up');
+ break;
+ case 'arrowdown':
+ UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), 'nudge down');
+ break;
}
return {
stopPropagation: stopPropagation,
- preventDefault: preventDefault
+ preventDefault: preventDefault,
};
});
@@ -183,15 +208,15 @@ export class KeyManager {
const preventDefault = true;
switch (keyname) {
- case "ƒ":
- case "f":
+ case 'ƒ':
+ case 'f':
const dv = SelectionManager.Views()?.[0];
- UndoManager.RunInBatch(() => dv.props.CollectionFreeFormDocumentView?.().float(), "float");
+ UndoManager.RunInBatch(() => dv.props.CollectionFreeFormDocumentView?.().float(), 'float');
}
return {
stopPropagation: stopPropagation,
- preventDefault: preventDefault
+ preventDefault: preventDefault,
};
});
@@ -200,83 +225,97 @@ export class KeyManager {
let preventDefault = true;
switch (keyname) {
- case "arrowright":
- if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ case 'arrowright':
+ if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
return { stopPropagation: false, preventDefault: false };
}
- MainView.Instance.mainFreeform && CollectionDockingView.AddSplit(MainView.Instance.mainFreeform, "right");
+ MainView.Instance.mainFreeform && CollectionDockingView.AddSplit(MainView.Instance.mainFreeform, 'right');
break;
- case "arrowleft":
- if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ case 'arrowleft':
+ if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
return { stopPropagation: false, preventDefault: false };
}
MainView.Instance.mainFreeform && CollectionDockingView.CloseSplit(MainView.Instance.mainFreeform);
break;
- case "backspace":
- if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
+ case 'backspace':
+ if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
return { stopPropagation: false, preventDefault: false };
}
break;
- case "t":
- PromiseValue(Cast(Doc.UserDoc()["tabs-button-tools"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
+ case 't':
+ PromiseValue(Cast(Doc.UserDoc()['tabs-button-tools'], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
break;
- case "f":
+ case 'f':
if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) {
- SelectionManager.Views()[0].ComponentView?.search?.("", false, false);
+ SelectionManager.Views()[0].ComponentView?.search?.('', false, false);
} else {
- const searchBtn = Doc.UserDoc().searchBtn as Doc;
+ const searchBtn = Doc.MySearcher;
if (searchBtn) {
MainView.Instance.selectMenu(searchBtn);
}
}
break;
- case "e": CurrentUserUtils.SelectedTool = InkTool.Eraser;
+ case 'e':
+ Doc.ActiveTool = InkTool.Eraser;
break;
- case "p": CurrentUserUtils.SelectedTool = InkTool.Pen;
+ case 'p':
+ Doc.ActiveTool = InkTool.Pen;
break;
- case "o":
+ case 'o':
const target = SelectionManager.Docs().lastElement();
target && CollectionDockingView.OpenFullScreen(target);
break;
- case "r":
+ case 'r':
preventDefault = false;
break;
- case "y":
+ case 'y':
SelectionManager.DeselectAll();
UndoManager.Redo();
stopPropagation = false;
break;
- case "z":
+ case 'z':
SelectionManager.DeselectAll();
UndoManager.Undo();
stopPropagation = false;
break;
- case "a":
+ case 'a':
if (e.target !== document.body) {
stopPropagation = false;
preventDefault = false;
}
break;
- case "v":
+ case 'v':
stopPropagation = false;
preventDefault = false;
break;
- case "x":
+ case 'x':
if (SelectionManager.Views().length) {
const bds = DocumentDecorations.Instance.Bounds;
- const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
- const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":");
+ const pt = SelectionManager.Views()[0]
+ .props.ScreenToLocalTransform()
+ .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+ const text =
+ `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` +
+ SelectionManager.Views()
+ .map(dv => dv.Document[Id])
+ .join(':');
SelectionManager.Views().length && navigator.clipboard.writeText(text);
- DocumentDecorations.Instance.onCloseClick();
+ DocumentDecorations.Instance.onCloseClick(true);
stopPropagation = false;
preventDefault = false;
}
break;
- case "c":
+ case 'c':
if (!AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
const bds = DocumentDecorations.Instance.Bounds;
- const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
- const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":");
+ const pt = SelectionManager.Views()[0]
+ .props.ScreenToLocalTransform()
+ .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+ const text =
+ `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` +
+ SelectionManager.Views()
+ .map(dv => dv.Document[Id])
+ .join(':');
SelectionManager.Views().length && navigator.clipboard.writeText(text);
stopPropagation = false;
}
@@ -286,44 +325,48 @@ export class KeyManager {
return {
stopPropagation: stopPropagation,
- preventDefault: preventDefault
+ preventDefault: preventDefault,
};
});
public paste(e: ClipboardEvent) {
- const plain = e.clipboardData?.getData("text/plain");
- const clone = plain?.startsWith("__DashCloneId(");
- if (plain && (plain.startsWith("__DashDocId(") || clone)) {
+ const plain = e.clipboardData?.getData('text/plain');
+ const clone = plain?.startsWith('__DashCloneId(');
+ if (plain && (plain.startsWith('__DashDocId(') || clone)) {
const first = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
if (first?.props.Document.type === DocumentType.COL) {
- const docids = plain.split(":");
+ const docids = plain.split(':');
let count = 1;
const list: Doc[] = [];
const targetDataDoc = Doc.GetProto(first.props.Document);
const fieldKey = first.LayoutFieldKey;
const docList = DocListCast(targetDataDoc[fieldKey]);
- docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => {
- count++;
- if (doc instanceof Doc) {
- list.push(doc);
- }
- if (count === docids.length) {
- const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => clone ? (await Doc.MakeClone(d)).clone : d));
- if (added.length) {
- added.map(doc => doc.context = targetDataDoc);
- undoBatch(() => {
- targetDataDoc[fieldKey] = new List<Doc>([...docList, ...added]);
- targetDataDoc[fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
- })();
- }
- }
- }));
+ docids.map(
+ (did, i) =>
+ i &&
+ DocServer.GetRefField(did).then(async doc => {
+ count++;
+ if (doc instanceof Doc) {
+ list.push(doc);
+ }
+ if (count === docids.length) {
+ const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d)).clone : d)));
+ if (added.length) {
+ added.map(doc => (doc.context = targetDataDoc));
+ undoBatch(() => {
+ targetDataDoc[fieldKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[fieldKey + '-lastModified'] = new DateField(new Date(Date.now()));
+ })();
+ }
+ }
+ })
+ );
}
}
}
- async printClipboard() {
- const text: string = await navigator.clipboard.readText();
+ getClipboard() {
+ return navigator.clipboard.readText();
}
private ctrl_shift = action((keyname: string) => {
@@ -331,15 +374,14 @@ export class KeyManager {
const preventDefault = true;
switch (keyname) {
- case "z":
+ case 'z':
UndoManager.Redo();
break;
}
return {
stopPropagation: stopPropagation,
- preventDefault: preventDefault
+ preventDefault: preventDefault,
};
});
-
}
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index a7f511ab4..d036a636a 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -21,7 +21,6 @@ export interface InkControlProps {
inkCtrlPoints: InkData;
screenCtrlPoints: InkData;
screenSpaceLineWidth: number;
- ScreenToLocalTransform: () => Transform;
nearestScreenPt: () => PointData | undefined;
}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 41cace1e3..821e2f739 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,23 +1,23 @@
-import { Bezier } from "bezier-js";
-import { Normalize, Distance } from "../util/bezierFit";
-import { action, observable, reaction } from "mobx";
-import { Doc, NumListCast, Opt } from "../../fields/Doc";
-import { InkData, InkField, InkTool, PointData } from "../../fields/InkField";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
-import { Cast, NumCast } from "../../fields/Types";
-import { Point } from "../../pen-gestures/ndollar";
-import { DocumentType } from "../documents/DocumentTypes";
-import { FitOneCurve } from "../util/bezierFit";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { DocumentManager } from "../util/DocumentManager";
-import { undoBatch } from "../util/UndoManager";
-import { InkingStroke } from "./InkingStroke";
-import { DocumentView } from "./nodes/DocumentView";
+import { Bezier } from 'bezier-js';
+import { action, observable, reaction } from 'mobx';
+import { Doc, NumListCast, Opt } from '../../fields/Doc';
+import { InkData, InkField, InkTool, PointData } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { Cast, NumCast } from '../../fields/Types';
+import { Point } from '../../pen-gestures/ndollar';
+import { DocumentType } from '../documents/DocumentTypes';
+import { FitOneCurve } from '../util/bezierFit';
+import { DocumentManager } from '../util/DocumentManager';
+import { undoBatch } from '../util/UndoManager';
+import { InkingStroke } from './InkingStroke';
+import { DocumentView } from './nodes/DocumentView';
export class InkStrokeProperties {
static _Instance: InkStrokeProperties | undefined;
- public static get Instance() { return this._Instance || new InkStrokeProperties(); }
+ public static get Instance() {
+ return this._Instance || new InkStrokeProperties();
+ }
@observable _lock = false;
@observable _controlButton = false;
@@ -25,8 +25,14 @@ export class InkStrokeProperties {
constructor() {
InkStrokeProperties._Instance = this;
- reaction(() => this._controlButton, button => button && (CurrentUserUtils.SelectedTool = InkTool.None));
- reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false));
+ reaction(
+ () => this._controlButton,
+ button => button && (Doc.ActiveTool = InkTool.None)
+ );
+ reaction(
+ () => Doc.ActiveTool,
+ tool => tool !== InkTool.None && (this._controlButton = false)
+ );
}
/**
@@ -34,35 +40,41 @@ export class InkStrokeProperties {
* @param func The inputted function.
* @param requireCurrPoint Indicates whether the current selected point is needed.
*/
- applyFunction = (strokes: Opt<DocumentView | DocumentView[]>, func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
+ applyFunction = (
+ strokes: Opt<DocumentView | DocumentView[]>,
+ func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number; Y: number }[] | undefined,
+ requireCurrPoint: boolean = false
+ ) => {
var appliedFunc = false;
- (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(action(inkView => {
- if (!requireCurrPoint || this._currentPoint !== -1) {
- const doc = inkView.rootDoc;
- if (doc.type === DocumentType.INK && doc.width && doc.height) {
- const ink = Cast(doc.data, InkField)?.inkData;
- if (ink) {
- const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
- const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
- const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1;
- const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1;
- const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth));
- if (newPoints) {
- const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X));
- const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y));
- doc._width = (newXrange.max - newXrange.min) * ptsXscale + NumCast(doc.strokeWidth);
- doc._height = (newYrange.max - newYrange.min) * ptsYscale + NumCast(doc.strokeWidth);
- doc.x = (oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale);
- doc.y = (oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale);
- Doc.GetProto(doc).data = new InkField(newPoints);
- appliedFunc = true;
+ (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(
+ action(inkView => {
+ if (!requireCurrPoint || this._currentPoint !== -1) {
+ const doc = inkView.rootDoc;
+ if (doc.type === DocumentType.INK && doc.width && doc.height) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
+ const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
+ const ptsXscale = (NumCast(doc._width) - NumCast(doc.strokeWidth)) / (oldXrange.max - oldXrange.min || 1) || 1;
+ const ptsYscale = (NumCast(doc._height) - NumCast(doc.strokeWidth)) / (oldYrange.max - oldYrange.min || 1) || 1;
+ const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth));
+ if (newPoints) {
+ const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X));
+ const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y));
+ doc._width = (newXrange.max - newXrange.min) * ptsXscale + NumCast(doc.strokeWidth);
+ doc._height = (newYrange.max - newYrange.min) * ptsYscale + NumCast(doc.strokeWidth);
+ doc.x = oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale;
+ doc.y = oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale;
+ Doc.GetProto(doc).data = new InkField(newPoints);
+ appliedFunc = true;
+ }
}
}
}
- }
- }));
+ })
+ );
return appliedFunc;
- }
+ };
/**
* Adds a new control point to the ink instance when editing its format.
@@ -72,7 +84,7 @@ export class InkStrokeProperties {
*/
@undoBatch
@action
- addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number, Y: number }[]) => {
+ addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => {
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const doc = view.rootDoc;
const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]];
@@ -81,12 +93,12 @@ export class InkStrokeProperties {
controls.splice(i, 4, ...splicepts.map(p => ({ X: p.x, Y: p.y })));
// Updating the indices of the control points whose handle tangency has been broken.
- doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control > i ? control + 4 : control));
+ doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec('number'), []).map(control => (control > i ? control + 4 : control)));
this._currentPoint = -1;
return controls;
});
- }
+ };
/**
* Scales a handle point of a control point that is adjacent to a newly added one.
@@ -107,15 +119,15 @@ export class InkStrokeProperties {
* the tangent vector to a control point is equivalent to the first/last (depending on the direction
* of the curve) leg of the Bézier curve's derivative.
* (Source: https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html)
- *
+ *
* @param C The curve represented by all points from the previous control until the newly added point.
* @param D The curve represented by all points from the newly added point to the next control.
* @param newControl The newly added control point.
*/
getNewHandlePoints = (C: PointData[], D: PointData[], newControl: PointData) => {
const [m, n] = [C.length, D.length];
- let handleSizeA = Math.sqrt((Math.pow(newControl.X - C[0].X, 2)) + (Math.pow(newControl.Y - C[0].Y, 2)));
- let handleSizeB = Math.sqrt((Math.pow(D[n - 1].X - newControl.X, 2)) + (Math.pow(D[n - 1].Y - newControl.Y, 2)));
+ let handleSizeA = Math.sqrt(Math.pow(newControl.X - C[0].X, 2) + Math.pow(newControl.Y - C[0].Y, 2));
+ let handleSizeB = Math.sqrt(Math.pow(D[n - 1].X - newControl.X, 2) + Math.pow(D[n - 1].Y - newControl.Y, 2));
// Scaling adjustments to improve the ratio between the magnitudes of the two handle lines.
// (Ensures that the new point added doesn't augment the inital shape of the curve much).
if (handleSizeA < 75 && handleSizeB < 75) {
@@ -131,50 +143,55 @@ export class InkStrokeProperties {
}
// Finding the last leg of the derivative curve of C.
const dC = { X: (handleSizeA / n) * (C[m - 1].X - C[m - 2].X), Y: (handleSizeA / n) * (C[m - 1].Y - C[m - 2].Y) };
- // Finding the first leg of the derivative curve of D.
+ // Finding the first leg of the derivative curve of D.
const dD = { X: (handleSizeB / m) * (D[1].X - D[0].X), Y: (handleSizeB / m) * (D[1].Y - D[0].Y) };
const handleA = { X: newControl.X - dC.X, Y: newControl.Y - dC.Y };
const handleB = { X: newControl.X + dD.X, Y: newControl.Y + dD.Y };
return [handleA, handleB];
- }
+ };
/**
* Deletes the current control point of the selected ink instance.
*/
@undoBatch
@action
- deletePoints = (inkView: DocumentView, preserve: boolean) => this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
- const doc = view.rootDoc;
- const newPoints = ink.slice();
- const brokenIndices = NumListCast(doc.brokenInkIndices);
- if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) {
- newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4);
- } else {
- const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
- const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
- const samples: Point[] = [];
- var startDir = { x: 0, y: 0 };
- var endDir = { x: 0, y: 0 };
- for (var i = 0; i < splicedPoints.length / 4; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === 0) startDir = bez.derivative(0);
- if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
- for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
- const pt = bez.compute(t);
- samples.push(new Point(pt.x, pt.y));
+ deletePoints = (inkView: DocumentView, preserve: boolean) =>
+ this.applyFunction(
+ inkView,
+ (view: DocumentView, ink: InkData) => {
+ const doc = view.rootDoc;
+ const newPoints = ink.slice();
+ const brokenIndices = NumListCast(doc.brokenInkIndices);
+ if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) {
+ newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4);
+ } else {
+ const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
+ const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
+ const samples: Point[] = [];
+ var startDir = { x: 0, y: 0 };
+ var endDir = { x: 0, y: 0 };
+ for (var i = 0; i < splicedPoints.length / 4; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === 0) startDir = bez.derivative(0);
+ if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
+ for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
+ const pt = bez.compute(t);
+ samples.push(new Point(pt.x, pt.y));
+ }
+ }
+ const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ if (error < 100) {
+ newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls);
+ } else {
+ newPoints.splice(this._currentPoint - 2, 4);
+ }
}
- }
- const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- if (error < 100) {
- newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls);
- } else {
- newPoints.splice(this._currentPoint - 2, 4);
- }
- }
- doc.brokenInkIndices = new List(brokenIndices.map(control => control >= this._currentPoint ? control - 4 : control));
- this._currentPoint = -1;
- return newPoints.length < 4 ? undefined : newPoints;
- }, true)
+ doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control)));
+ this._currentPoint = -1;
+ return newPoints.length < 4 ? undefined : newPoints;
+ },
+ true
+ );
/**
* Rotates ink stroke(s) about a point
@@ -188,15 +205,16 @@ export class InkStrokeProperties {
this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
view.rootDoc.rotation = NumCast(view.rootDoc.rotation) + angle;
const inkCenterPt = view.ComponentView?.ptFromScreen?.(scrpt);
- return !inkCenterPt ? ink :
- ink.map(i => {
- const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y };
- const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale;
- const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y;
- return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y };
- });
+ return !inkCenterPt
+ ? ink
+ : ink.map(i => {
+ const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y };
+ const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * yScale) / xScale;
+ const newY = (Math.sin(angle) * pt.X * xScale) / yScale + Math.cos(angle) * pt.Y;
+ return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y };
+ });
});
- }
+ };
/**
* Rotates ink stroke(s) about a point
@@ -207,19 +225,20 @@ export class InkStrokeProperties {
@undoBatch
@action
stretchInk = (inkStrokes: DocumentView[], scaling: number, scrpt: PointData, scrVec: PointData, scaleUniformly: boolean) => {
- this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
+ this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData) => {
const ptFromScreen = view.ComponentView?.ptFromScreen;
const ptToScreen = view.ComponentView?.ptToScreen;
- return !ptToScreen || !ptFromScreen ? ink :
- ink.map(ptToScreen).map(i => {
- const pvec = { X: i.X - scrpt.X, Y: i.Y - scrpt.Y };
- const svec = pvec.X * scrVec.X * scaling + pvec.Y * scrVec.Y * scaling;
- const ovec = -pvec.X * scrVec.Y * (scaleUniformly ? scaling : 1) + pvec.Y * scrVec.X * (scaleUniformly ? scaling : 1);
- const newscrpt = { X: scrpt.X + svec * scrVec.X - ovec * scrVec.Y, Y: scrpt.Y + svec * scrVec.Y + ovec * scrVec.X };
- return ptFromScreen(newscrpt);
- });
+ return !ptToScreen || !ptFromScreen
+ ? ink
+ : ink.map(ptToScreen).map(i => {
+ const pvec = { X: i.X - scrpt.X, Y: i.Y - scrpt.Y };
+ const svec = pvec.X * scrVec.X * scaling + pvec.Y * scrVec.Y * scaling;
+ const ovec = -pvec.X * scrVec.Y * (scaleUniformly ? scaling : 1) + pvec.Y * scrVec.X * (scaleUniformly ? scaling : 1);
+ const newscrpt = { X: scrpt.X + svec * scrVec.X - ovec * scrVec.Y, Y: scrpt.Y + svec * scrVec.Y + ovec * scrVec.X };
+ return ptFromScreen(newscrpt);
+ });
});
- }
+ };
/**
* Handles the movement/scaling of a control point.
@@ -227,56 +246,55 @@ export class InkStrokeProperties {
@undoBatch
@action
moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) =>
- this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => {
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const order = controlIndex % 4;
const closed = InkingStroke.IsClosed(ink);
- if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1) {
+ const brokenIndices = Cast(inkView.props.Document.brokenInkIndices, listSpec('number'), []);
+ if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
const cpt_before = ink[controlIndex];
const cpt = { X: cpt_before.X + deltaX, Y: cpt_before.Y + deltaY };
- if (true) {
- const newink = origInk.slice();
- const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
- const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
- const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
- const samplesLeft: Point[] = [];
- const samplesRight: Point[] = [];
- var startDir = { x: 0, y: 0 };
- var endDir = { x: 0, y: 0 };
- for (var i = 0; i < nearestSeg / 4 + 1; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === 0) startDir = bez.derivative(0);
- if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
- for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + .05 : 1); t += 0.05) {
- const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
- samplesLeft.push(new Point(pt.x, pt.y));
- }
+ const newink = origInk.slice();
+ const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
+ const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
+ const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
+ if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1)) return ink.slice();
+ const samplesLeft: Point[] = [];
+ const samplesRight: Point[] = [];
+ var startDir = { x: 0, y: 0 };
+ var endDir = { x: 0, y: 0 };
+ for (var i = 0; i < nearestSeg / 4 + 1; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === 0) startDir = bez.derivative(0);
+ if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
+ for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
+ const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
+ samplesLeft.push(new Point(pt.x, pt.y));
}
- var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
- if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
- for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + .05 + 1e-7 : 1 + 1e-7); t += 0.05) {
- const pt = bez.compute(Math.min(1, t));
- samplesRight.push(new Point(pt.x, pt.y));
- }
+ }
+ var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
+ if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
+ for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
+ const pt = bez.compute(Math.min(1, t));
+ samplesRight.push(new Point(pt.x, pt.y));
}
- const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- finalCtrls = finalCtrls.concat(rightCtrls);
- newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
- return newink;
}
+ const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ finalCtrls = finalCtrls.concat(rightCtrls);
+ newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
+ return newink;
}
return ink.map((pt, i) => {
const leftHandlePoint = order === 0 && i === controlIndex + 1;
const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
- if (controlIndex === i ||
- (order === 0 && controlIndex !== 0 && i === controlIndex - 1) ||
- (order === 3 && i === controlIndex - 1)) {
- return ({ X: pt.X + deltaX, Y: pt.Y + deltaY });
+ if (controlIndex === i || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || (order === 3 && i === controlIndex - 1)) {
+ return { X: pt.X + deltaX, Y: pt.Y + deltaY };
}
- if (controlIndex === i ||
+ if (
+ controlIndex === i ||
leftHandlePoint ||
rightHandlePoint ||
(order === 0 && controlIndex !== 0 && i === controlIndex - 1) ||
@@ -284,15 +302,15 @@ export class InkStrokeProperties {
(order === 3 && i === controlIndex - 1) ||
(order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
(order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
- ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) {
- return ({ X: pt.X + deltaX, Y: pt.Y + deltaY });
+ (ink[0].X === ink[ink.length - 1].X && ink[0].Y === ink[ink.length - 1].Y && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))
+ ) {
+ return { X: pt.X + deltaX, Y: pt.Y + deltaY };
}
return pt;
});
- })
-
+ });
- public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refInkSpacePt: { X: number, Y: number }, excludeSegs?: number[]) {
+ public static nearestPtToStroke(ctrlPoints: { X: number; Y: number }[], refInkSpacePt: { X: number; Y: number }, excludeSegs?: number[]) {
var distance = Number.MAX_SAFE_INTEGER;
var nearestT = -1;
var nearestSeg = -1;
@@ -326,16 +344,16 @@ export class InkStrokeProperties {
if (screenDragPt) {
const snapData = this.snapToAllCurves(screenDragPt, inkView, { nearestPt: { X: 0, Y: 0 }, distance: 10 }, ink, controlIndex);
if (snapData.distance < 10) {
- const deltaX = (snapData.nearestPt.X - ink[controlIndex].X);
- const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y);
+ const deltaX = snapData.nearestPt.X - ink[controlIndex].X;
+ const deltaY = snapData.nearestPt.Y - ink[controlIndex].Y;
const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex, ink.slice());
- console.log("X = " + snapData.nearestPt.X + " " + snapData.nearestPt.Y);
+ console.log('X = ' + snapData.nearestPt.X + ' ' + snapData.nearestPt.Y);
return res;
}
}
}
return false;
- }
+ };
excludeSelfSnapSegs = (ink: InkData, controlIndex: number) => {
const closed = InkingStroke.IsClosed(ink);
@@ -346,9 +364,9 @@ export class InkStrokeProperties {
const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1;
const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1;
return [thisseg, prevseg, nextseg];
- }
+ };
- snapToAllCurves = (screenDragPt: { X: number, Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number, Y: number }, distance: number }, ink: InkData, controlIndex: number) => {
+ snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => {
const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
containingCollection?.childDocs
.filter(doc => doc.type === DocumentType.INK)
@@ -356,8 +374,7 @@ export class InkStrokeProperties {
const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView);
const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
if (snapped && snapped.distance < snapData.distance) {
- const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt :
- inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
+ const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
if (snappedInkPt) {
snapData = { nearestPt: snappedInkPt, distance: snapped.distance };
@@ -365,7 +382,7 @@ export class InkStrokeProperties {
}
});
return snapData;
- }
+ };
/**
* Snaps a control point with broken tangency back to synced rotation.
@@ -375,7 +392,7 @@ export class InkStrokeProperties {
snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => {
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const doc = view.rootDoc;
- const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []);
+ const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'), []);
const ind = brokenIndices.findIndex(value => value === controlIndex);
if (ind !== -1) {
brokenIndices.splice(ind, 1);
@@ -387,7 +404,7 @@ export class InkStrokeProperties {
return inkCopy;
}
});
- }
+ };
/**
* Rotates the target point about the origin point for a given angle (radians).
@@ -398,11 +415,11 @@ export class InkStrokeProperties {
const newX = Math.cos(angle) * rotatedTarget.X - Math.sin(angle) * rotatedTarget.Y;
const newY = Math.sin(angle) * rotatedTarget.X + Math.cos(angle) * rotatedTarget.Y;
return { X: newX + origin.X, Y: newY + origin.Y };
- }
+ };
/**
* Finds the angle (in radians) between two inputted vectors.
- *
+ *
* α = arccos(a·b / |a|·|b|), where a and b are both vectors.
*/
public static angleBetweenTwoVectors(vectorA: PointData, vectorB: PointData) {
@@ -435,23 +452,22 @@ export class InkStrokeProperties {
@undoBatch
@action
moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
- this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => {
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const doc = view.rootDoc;
const closed = InkingStroke.IsClosed(ink);
const oldHandlePoint = ink[handleIndex];
const oppositeHandlePoint = ink[oppositeHandleIndex];
const controlPoint = ink[controlIndex];
- const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale };
+ const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY };
const inkCopy = ink.slice();
inkCopy[handleIndex] = newHandlePoint;
- const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
+ const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'));
const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1;
// Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle).
- if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) &&
- (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) {
+ if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) {
const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
}
return inkCopy;
- })
-} \ No newline at end of file
+ });
+}
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index ab73e58a4..ae35bc980 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -13,7 +13,6 @@ import { Colors } from "./global/globalEnums";
import { InkingStroke } from "./InkingStroke";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { DocumentView } from "./nodes/DocumentView";
-
export interface InkHandlesProps {
inkDoc: Doc;
inkView: DocumentView;
@@ -29,8 +28,10 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
* @param handleNum The index of the currently selected handle point.
*/
onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
- var controlUndo: UndoManager.Batch | undefined;
+ const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen;
+ if (!ptFromScreen) return;
const screenScale = this.props.ScreenToLocalTransform().Scale;
+ var controlUndo: UndoManager.Batch | undefined;
const order = handleIndex % 4;
const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
@@ -39,7 +40,9 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
(e: PointerEvent, down: number[], delta: number[]) => {
if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent");
if (e.altKey) this.onBreakTangent(controlIndex);
- InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
+ const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
+ const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
+ InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
return false;
}, () => {
controlUndo?.end();
@@ -99,7 +102,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
r={screenSpaceLineWidth * 2}
fill={Colors.MEDIUM_BLUE}
strokeWidth={1}
- stroke={Colors.MEDIUM_BLUE}
+ stroke={Colors.BLACK}
onPointerDown={e => this.onHandleDown(e, pts.I)}
pointerEvents="all"
cursor="default"
diff --git a/src/client/views/InkTranscription.scss b/src/client/views/InkTranscription.scss
new file mode 100644
index 000000000..bbb0a1afa
--- /dev/null
+++ b/src/client/views/InkTranscription.scss
@@ -0,0 +1,5 @@
+.ink-transcription {
+ .error-msg {
+ display: none !important;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
new file mode 100644
index 000000000..bf0e8081d
--- /dev/null
+++ b/src/client/views/InkTranscription.tsx
@@ -0,0 +1,347 @@
+import * as iink from 'iink-js';
+import { action, observable } from 'mobx';
+import * as React from 'react';
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc';
+import { InkData, InkField, InkTool } from '../../fields/InkField';
+import { Cast, DateCast, NumCast } from '../../fields/Types';
+import { aggregateBounds } from '../../Utils';
+import { DocumentType } from '../documents/DocumentTypes';
+import { DocumentManager } from '../util/DocumentManager';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { InkingStroke } from './InkingStroke';
+import './InkTranscription.scss';
+
+/**
+ * Class component that handles inking in writing mode
+ */
+export class InkTranscription extends React.Component {
+ static Instance: InkTranscription;
+
+ @observable _mathRegister: any;
+ @observable _mathRef: any;
+ @observable _textRegister: any;
+ @observable _textRef: any;
+ private lastJiix: any;
+ private currGroup?: Doc;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ InkTranscription.Instance = this;
+ }
+
+ componentWillUnmount() {
+ this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+ this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+ }
+
+ @action
+ setMathRef = (r: any) => {
+ if (!this._mathRegister) {
+ this._mathRegister = r
+ ? iink.register(r, {
+ recognitionParams: {
+ type: 'MATH',
+ protocol: 'WEBSOCKET',
+ server: {
+ host: 'cloud.myscript.com',
+ applicationKey: process.env.IINKJS_APP,
+ hmacKey: process.env.IINKJS_HMAC,
+ websocket: {
+ pingEnabled: false,
+ autoReconnect: true,
+ },
+ },
+ iink: {
+ math: {
+ mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'],
+ },
+ export: {
+ jiix: {
+ strokes: true,
+ },
+ },
+ },
+ },
+ })
+ : null;
+ }
+
+ r.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+
+ return (this._mathRef = r);
+ };
+
+ @action
+ setTextRef = (r: any) => {
+ if (!this._textRegister) {
+ this._textRegister = r
+ ? iink.register(r, {
+ recognitionParams: {
+ type: 'TEXT',
+ protocol: 'WEBSOCKET',
+ server: {
+ host: 'cloud.myscript.com',
+ applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f',
+ hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1',
+ websocket: {
+ pingEnabled: false,
+ autoReconnect: true,
+ },
+ },
+ iink: {
+ text: {
+ mimeTypes: ['text/plain'],
+ },
+ export: {
+ jiix: {
+ strokes: true,
+ },
+ },
+ },
+ },
+ })
+ : null;
+ }
+
+ r.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+
+ return (this._textRef = r);
+ };
+
+ /**
+ * Handles processing Dash Doc data for ink transcription.
+ *
+ * @param groupDoc the group which contains the ink strokes we want to transcribe
+ * @param inkDocs the ink docs contained within the selected group
+ * @param math boolean whether to do math transcription or not
+ */
+ transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => {
+ if (!groupDoc) return;
+ const validInks = inkDocs.filter(s => s.type === DocumentType.INK);
+
+ const strokes: InkData[] = [];
+ const times: number[] = [];
+ validInks
+ .filter(i => Cast(i.data, InkField))
+ .forEach(i => {
+ const d = Cast(i.data, InkField, null);
+ const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke;
+ strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
+ times.push(DateCast(i.creationDate).getDate().getTime());
+ });
+
+ this.currGroup = groupDoc;
+
+ const pointerData = { events: strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) };
+ const processGestures = false;
+
+ if (math) {
+ this._mathRef.editor.pointerEvents(pointerData, processGestures);
+ } else {
+ this._textRef.editor.pointerEvents(pointerData, processGestures);
+ }
+ };
+
+ /**
+ * Converts the Dash Ink Data to JSON.
+ *
+ * @param stroke The dash ink data
+ * @param time the time of the stroke
+ * @returns json object representation of ink data
+ */
+ inkJSON = (stroke: InkData, time: number) => {
+ return {
+ pointerType: 'PEN',
+ pointerId: 1,
+ x: stroke.map(point => point.X),
+ y: stroke.map(point => point.Y),
+ t: new Array(stroke.length).fill(time),
+ p: new Array(stroke.length).fill(1.0),
+ };
+ };
+
+ /**
+ * Creates subgroups for each word for the whole text transcription
+ * @param wordInkDocMap the mapping of words to ink strokes (Ink Docs)
+ */
+ subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => {
+ // iterate through the keys of wordInkDocMap
+ wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => {
+ const selected = inkDocs.slice();
+ if (!selected) {
+ return;
+ }
+ const ctx = await Cast(selected[0].context, Doc);
+ if (!ctx) {
+ return;
+ }
+ const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView;
+
+ if (!docView) return;
+ const marqViewRef = docView._marqueeViewRef.current;
+ if (!marqViewRef) return;
+ this.groupInkDocs(selected, docView, word);
+ });
+ };
+
+ /**
+ * Event listener function for when the 'exported' event is heard.
+ *
+ * @param e the event objects
+ * @param ref the ref to the editor
+ */
+ exportInk = (e: any, ref: any) => {
+ const exports = e.detail.exports;
+ if (exports) {
+ if (exports['application/x-latex']) {
+ const latex = exports['application/x-latex'];
+ if (this.currGroup) {
+ this.currGroup.text = latex;
+ this.currGroup.title = latex;
+ }
+
+ ref.editor.clear();
+ } else if (exports['text/plain']) {
+ if (exports['application/vnd.myscript.jiix']) {
+ this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']);
+ // map timestamp to strokes
+ const timestampWord = new Map<number, string>();
+ this.lastJiix.words.map((word: any) => {
+ if (word.items) {
+ word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => {
+ const ms = Date.parse(i.timestamp);
+ timestampWord.set(ms, word.label);
+ });
+ }
+ });
+
+ const wordInkDocMap = new Map<string, Doc[]>();
+ if (this.currGroup) {
+ const docList = DocListCast(this.currGroup.data);
+ docList.forEach((inkDoc: Doc) => {
+ // just having the times match up and be a unique value (actual timestamp doesn't matter)
+ const ms = DateCast(inkDoc.creationDate).getDate().getTime() + 14400000;
+ const word = timestampWord.get(ms);
+ if (!word) {
+ return;
+ }
+ const entry = wordInkDocMap.get(word);
+ if (entry) {
+ entry.push(inkDoc);
+ wordInkDocMap.set(word, entry);
+ } else {
+ const newEntry = [inkDoc];
+ wordInkDocMap.set(word, newEntry);
+ }
+ });
+ if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap);
+ }
+ }
+ const text = exports['text/plain'];
+
+ if (this.currGroup) {
+ this.currGroup.transcription = text;
+ this.currGroup.title = text.split('\n')[0];
+ }
+
+ ref.editor.clear();
+ }
+ }
+ };
+
+ /**
+ * Creates the ink grouping once the user leaves the writing mode.
+ */
+ createInkGroup() {
+ // TODO nda - if document being added to is a inkGrouping then we can just add to that group
+ if (Doc.ActiveTool === InkTool.Write) {
+ CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+ // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+ const selected = ffView.unprocessedDocs;
+ const newCollection = this.groupInkDocs(selected, ffView);
+ ffView.unprocessedDocs = [];
+
+ InkTranscription.Instance.transcribeInk(newCollection, selected, false);
+ });
+ }
+ CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+ }
+
+ /**
+ * Creates the groupings for a given list of ink docs on a specific doc view
+ * @param selected: the list of ink docs to create a grouping of
+ * @param docView: the view in which we want the grouping to be created
+ * @param word: optional param if the group we are creating is a word (subgrouping individual words)
+ * @returns a new collection Doc or undefined if the grouping fails
+ */
+ groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
+ const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
+
+ // calculate the necessary bounds from the selected ink docs
+ selected.map(
+ action(d => {
+ const x = NumCast(d.x);
+ const y = NumCast(d.y);
+ const width = d[WidthSym]();
+ const height = d[HeightSym]();
+ bounds.push({ x, y, width, height });
+ })
+ );
+
+ // calculate the aggregated bounds
+ const aggregBounds = aggregateBounds(bounds, 0, 0);
+ const marqViewRef = docView._marqueeViewRef.current;
+
+ // set the vals for bounds in marqueeView
+ if (marqViewRef) {
+ marqViewRef._downX = aggregBounds.x;
+ marqViewRef._downY = aggregBounds.y;
+ marqViewRef._lastX = aggregBounds.r;
+ marqViewRef._lastY = aggregBounds.b;
+ }
+
+ // map through all the selected ink strokes and create the groupings
+ selected.map(
+ action(d => {
+ const dx = NumCast(d.x);
+ const dy = NumCast(d.y);
+ delete d.x;
+ delete d.y;
+ delete d.activeFrame;
+ delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ // calculate pos based on bounds
+ if (marqViewRef?.Bounds) {
+ d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+ d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+ }
+ return d;
+ })
+ );
+ docView.props.removeDocument?.(selected);
+ // Gets a collection based on the selected nodes using a marquee view ref
+ const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+ if (newCollection) {
+ newCollection.height = newCollection[HeightSym]();
+ newCollection.width = newCollection[WidthSym]();
+ // if the grouping we are creating is an individual word
+ if (word) {
+ newCollection.title = word;
+ }
+ }
+
+ // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+ newCollection && docView.props.addDocument?.(newCollection);
+ return newCollection;
+ }
+
+ render() {
+ return (
+ <div className="ink-transcription">
+ <div className="math-editor" ref={this.setMathRef} touch-action="none"></div>
+ <div className="text-editor" ref={this.setTextRef} touch-action="none"></div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 40fe6aa68..e5de7a0c5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,34 +20,37 @@
Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
*/
-import React = require("react");
-import { action, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, WidthSym } from "../../fields/Doc";
-import { InkData, InkField, InkTool } from "../../fields/InkField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
-import { TraceMobx } from "../../fields/util";
-import { OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { InteractionUtils } from "../util/InteractionUtils";
-import { SnappingManager } from "../util/SnappingManager";
-import { Transform } from "../util/Transform";
-import { UndoManager } from "../util/UndoManager";
-import { ContextMenu } from "./ContextMenu";
-import { ViewBoxBaseComponent } from "./DocComponent";
-import { Colors } from "./global/globalEnums";
-import { InkControlPtHandles, InkEndPtHandles } from "./InkControlPtHandles";
-import "./InkStroke.scss";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { InkTangentHandles } from "./InkTangentHandles";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
-import Color = require("color");
+import React = require('react');
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, WidthSym } from '../../fields/Doc';
+import { InkData, InkField, InkTool } from '../../fields/InkField';
+import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { TraceMobx } from '../../fields/util';
+import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { CognitiveServices } from '../cognitive_services/CognitiveServices';
+import { InteractionUtils } from '../util/InteractionUtils';
+import { SnappingManager } from '../util/SnappingManager';
+import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
+import { ContextMenu } from './ContextMenu';
+import { ViewBoxBaseComponent } from './DocComponent';
+import { Colors } from './global/globalEnums';
+import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { InkTangentHandles } from './InkTangentHandles';
+import { DocComponentView } from './nodes/DocumentView';
+import { FieldView, FieldViewProps } from './nodes/FieldView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import './InkStroke.scss';
+import Color = require('color');
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(InkingStroke, fieldStr);
+ }
public static IsClosed(inkData: InkData) {
return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
}
@@ -55,38 +58,52 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
private _selDisposer?: IReactionDisposer;
@observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
- @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
- @observable _nearestScrPt?: { X: number, Y: number }; // nearst screen point on the ink stroke ""
+ @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
+ @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke ""
componentDidMount() {
this.props.setContentView?.(this);
- this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
- selected => !selected && (InkStrokeProperties.Instance._controlButton = false));
+ this._selDisposer = reaction(
+ () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
+ selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
+ );
}
componentWillUnmount() {
this._selDisposer?.();
}
+ // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
+ // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
+ screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1);
+
+ getAnchor = () => {
+ console.log(document.activeElement);
+ return this._subContentView?.getAnchor?.() || this.rootDoc;
+ };
+
+ scrollFocus = (textAnchor: Doc, smooth: boolean) => {
+ return this._subContentView?.scrollFocus?.(textAnchor, smooth);
+ };
/**
- * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
- * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
- * the center of the ink stroke changes as the stroke is rotated.
- */
+ * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
+ * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
+ * the center of the ink stroke changes as the stroke is rotated.
+ */
getCenter = (xf: Transform) => {
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
const angle = -NumCast(this.layoutDoc.rotation);
const newPoints = inkData.map(pt => {
- const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * inkScaleY / inkScaleX;
- const newY = Math.sin(angle) * pt.X * inkScaleX / inkScaleY + Math.cos(angle) * pt.Y;
+ const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * inkScaleY) / inkScaleX;
+ const newY = (Math.sin(angle) * pt.X * inkScaleX) / inkScaleY + Math.cos(angle) * pt.Y;
return { X: newX, Y: newY };
});
const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2;
const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2;
- const cx = Math.cos(-angle) * crx - Math.sin(-angle) * cry * inkScaleY / inkScaleX;
- const cy = Math.sin(-angle) * crx * inkScaleX / inkScaleY + Math.cos(-angle) * cry;
+ const cx = Math.cos(-angle) * crx - (Math.sin(-angle) * cry * inkScaleY) / inkScaleX;
+ const cy = (Math.sin(-angle) * crx * inkScaleX) / inkScaleY + Math.cos(-angle) * cry;
const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2);
return { X: tc[0], Y: tc[1] };
- }
+ };
/**
* analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field,
@@ -94,7 +111,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
*/
analyzeStrokes() {
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
- CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
+ CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]);
}
/**
@@ -102,12 +119,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
* When displayed as a mask, the stroke is rendered with mixBlendMode set to multiply so that the stroke will
* appear to illuminate what it covers up. At the same time, all pixels that are not under the stroke will be
* dimmed by a semi-opaque overlay mask.
- */
+ */
public static toggleMask = action((inkDoc: Doc) => {
inkDoc.isInkMask = !inkDoc.isInkMask;
- inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
- inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
- inkDoc.color = "#9b9b9bff";
+ inkDoc._backgroundColor = inkDoc.isInkMask ? 'rgba(0,0,0,0.7)' : undefined;
+ inkDoc.mixBlendMode = inkDoc.isInkMask ? 'hard-light' : undefined;
+ inkDoc.color = '#9b9b9bff';
inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
});
/**
@@ -119,86 +136,100 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
this._handledClick = false;
const inkView = this.props.docViewPath().lastElement();
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
- (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const screenPts = inkData
+ .map(point =>
+ this.screenToLocal()
+ .inverse()
+ .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+ )
+ .map(p => ({ X: p[0], Y: p[1] }));
const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
const controlIndex = nearestSeg;
const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
var controlUndo: UndoManager.Batch | undefined;
const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
- setupMoveUpEvents(this, e,
- !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
- if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
- const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
- const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
- InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
- InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
- return false;
- }),
- !isEditing ? returnFalse : action(() => {
- controlUndo?.end();
- controlUndo = undefined;
- UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
- }),
+ setupMoveUpEvents(
+ this,
+ e,
+ !isEditing
+ ? returnFalse
+ : action((e: PointerEvent, down: number[], delta: number[]) => {
+ if (!controlUndo) controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+ const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
+ const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
+ InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+ InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
+ return false;
+ }),
+ !isEditing
+ ? returnFalse
+ : action(() => {
+ controlUndo?.end();
+ controlUndo = undefined;
+ UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
+ }),
action((e: PointerEvent, doubleTap: boolean | undefined) => {
doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
if (doubleTap) {
InkStrokeProperties.Instance._controlButton = true;
InkStrokeProperties.Instance._currentPoint = -1;
- this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
+ this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
if (isEditing) {
this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
}
}
- }), isEditing, isEditing, action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1)));
- }
+ }),
+ isEditing,
+ isEditing,
+ action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1))
+ );
+ };
/**
* @param scrPt a point in the screen coordinate space
* @returns the point in the ink data's coordinate space.
*/
- ptFromScreen = (scrPt: { X: number, Y: number }) => {
+ ptFromScreen = (scrPt: { X: number; Y: number }) => {
const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const docPt = this.props.ScreenToLocalTransform().transformPoint(scrPt.X, scrPt.Y);
+ const docPt = this.screenToLocal().transformPoint(scrPt.X, scrPt.Y);
const inkPt = {
X: (docPt[0] - inkStrokeWidth / 2) / inkScaleX + inkStrokeWidth / 2 + inkLeft,
Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop,
};
return inkPt;
- }
+ };
/**
* @param inkPt a point in the ink data's coordinate space
* @returns the screen point corresponding to the ink point
*/
- ptToScreen = (inkPt: { X: number, Y: number }) => {
+ ptToScreen = (inkPt: { X: number; Y: number }) => {
const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
const docPt = {
X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2
+ Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2,
};
- const scrPt = this.props.ScreenToLocalTransform().inverse().transformPoint(docPt.X, docPt.Y);
+ const scrPt = this.screenToLocal().inverse().transformPoint(docPt.X, docPt.Y);
return { X: scrPt[0], Y: scrPt[1] };
- }
+ };
/**
- * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
- * @param scrPt - the point to snap to this stroke
- * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
- *
- * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
- */
- snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => {
+ * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
+ * @param scrPt - the point to snap to this stroke
+ * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
+ *
+ * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
+ */
+ snapPt = (scrPt: { X: number; Y: number }, excludeSegs?: number[]) => {
const { inkData } = this.inkScaledData();
const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []);
- return { nearestPt, distance: distance * this.props.ScreenToLocalTransform().inverse().Scale };
- }
+ return { nearestPt, distance: distance * this.screenToLocal().inverse().Scale };
+ };
/**
- * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
- * factor for converting between ink and screen space.
- */
+ * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
+ * factor for converting between ink and screen space.
+ */
inkScaledData = () => {
const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
@@ -215,23 +246,31 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
inkLeft,
inkWidth,
inkHeight,
- inkScaleX: ((this.props.PanelWidth() - inkStrokeWidth) / ((inkWidth - inkStrokeWidth) || 1) || 1),
- inkScaleY: ((this.props.PanelHeight() - inkStrokeWidth) / ((inkHeight - inkStrokeWidth) || 1) || 1)
+ inkScaleX: (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1,
+ inkScaleY: (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1,
};
- }
+ };
+ //
+ // this updates the highlight for the nearest point on the curve to the cursor.
+ // if the user double clicks, this highlighted point will be added as a control point in the curve.
+ //
@action
onPointerMove = (e: React.PointerEvent) => {
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
- (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const screenPts = inkData
+ .map(point =>
+ this.screenToLocal()
+ .inverse()
+ .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+ )
+ .map(p => ({ X: p[0], Y: p[1] }));
const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
this._nearestT = nearestT;
this._nearestSeg = nearestSeg;
this._nearestScrPt = nearestPt;
- }
+ };
/**
* @returns the nearest screen point to the cursor (to render a highlight for the point to be added)
@@ -246,48 +285,66 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
componentUI = (boundsLeft: number, boundsTop: number) => {
const inkDoc = this.props.Document;
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.props.ScreenToLocalTransform().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
+ const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
- const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
- const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
- (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const screenInkWidth = this.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
+ const screenPts = inkData
+ .map(point =>
+ this.screenToLocal()
+ .inverse()
+ .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
+ )
+ .map(p => ({ X: p[0], Y: p[1] }));
const screenHdlPts = screenPts;
const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
const markerScale = NumCast(this.layoutDoc.strokeMarkerScale);
- return SnappingManager.GetIsDragging() ? (null) :
- !InkStrokeProperties.Instance._controlButton ?
- (!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? (null) :
- <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
- <InkEndPtHandles
- inkView={this.props.docViewPath().lastElement()}
- inkDoc={inkDoc}
- startPt={this.ptToScreen(inkData[0])}
- endPt={this.ptToScreen(inkData.lastElement())}
- screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /></div>) :
+ return SnappingManager.GetIsDragging() ? null : !InkStrokeProperties.Instance._controlButton ? (
+ !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
<div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
- {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
- StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
- "none", startMarker, endMarker, markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth), StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
- <InkControlPtHandles
- inkView={this.props.docViewPath().lastElement()}
- inkDoc={inkDoc}
- inkCtrlPoints={inkData}
- screenCtrlPoints={screenHdlPts}
- nearestScreenPt={this.nearestScreenPt}
- screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
- <InkTangentHandles
- inkView={this.props.docViewPath().lastElement()}
- inkDoc={inkDoc}
- screenCtrlPoints={screenHdlPts}
- screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
- </div>;
- }
+ <InkEndPtHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
+ </div>
+ )
+ ) : (
+ <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
+ {InteractionUtils.CreatePolyline(
+ screenPts,
+ 0,
+ 0,
+ Colors.MEDIUM_BLUE,
+ screenInkWidth[0],
+ screenSpaceCenterlineStrokeWidth,
+ StrCast(inkDoc.strokeLineJoin),
+ StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(inkDoc.strokeBezier),
+ 'none',
+ startMarker,
+ endMarker,
+ markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth),
+ StrCast(inkDoc.strokeDash),
+ 1,
+ 1,
+ '',
+ 'none',
+ 1.0,
+ false
+ )}
+ <InkControlPtHandles
+ inkView={this.props.docViewPath().lastElement()}
+ inkDoc={inkDoc}
+ inkCtrlPoints={inkData}
+ screenCtrlPoints={screenHdlPts}
+ nearestScreenPt={this.nearestScreenPt}
+ screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
+ />
+ <InkTangentHandles inkView={this.props.docViewPath().lastElement()} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
+ </div>
+ );
+ };
+ _subContentView: DocComponentView | undefined;
+ setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
render() {
TraceMobx();
const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
@@ -296,84 +353,181 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1);
const closed = InkingStroke.IsClosed(inkData);
- const fillColor = StrCast(this.layoutDoc.fillColor, "transparent");
- const strokeColor = !closed && fillColor && fillColor !== "transparent" ? fillColor : StrCast(this.layoutDoc.color);
+ const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent');
+ const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
// Visually renders the polygonal line made by the user.
- const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth,
- StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
- StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
- markerScale, StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false);
- const highlightIndex = BoolCast(this.props.Document.isLinkButton) && Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
- const highlightColor = !highlightIndex ?
- StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") :
- ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
+ const inkLine = InteractionUtils.CreatePolyline(
+ inkData,
+ inkLeft,
+ inkTop,
+ strokeColor,
+ inkStrokeWidth,
+ inkStrokeWidth,
+ StrCast(this.layoutDoc.strokeLineJoin),
+ StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(this.layoutDoc.strokeBezier),
+ !closed ? 'none' : fillColor === 'transparent' ? 'none' : fillColor,
+ startMarker,
+ endMarker,
+ markerScale,
+ StrCast(this.layoutDoc.strokeDash),
+ inkScaleX,
+ inkScaleY,
+ '',
+ 'none',
+ 1.0,
+ false
+ );
+ const highlightIndex = /*BoolCast(this.props.Document.isLinkButton) && */ Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
+ const highlightColor = !highlightIndex
+ ? StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent')
+ : ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'yellow', 'magenta', 'cyan', 'orange'][highlightIndex];
// Invisible polygonal line that enables the ink to be selected by the user.
- const clickableLine = (downHdlr?: (e: React.PointerEvent) => void) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
- inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && fillColor && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
- StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
- StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
- markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0,
- false, downHdlr);
-
- return <div className="inkStroke-wrapper">
- <svg className="inkStroke"
- style={{
- transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
- mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
- cursor: this.props.isSelected() ? "default" : undefined
- }}
- onPointerLeave={action(e => this._nearestScrPt = undefined)}
- onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined}
- onClick={e => this._handledClick && e.stopPropagation()}
- onContextMenu={() => {
- const cm = ContextMenu.Instance;
- !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
- cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
- cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
- }}
- >
- {clickableLine(this.onPointerDown)}
- {inkLine}
- </svg>
- {!closed ? (null) :
- <div className="inkStroke-text" style={{
- color: StrCast(this.layoutDoc.textColor, "black"),
- pointerEvents: this.props.isDocumentActive?.() ? "all" : undefined,
- width: this.layoutDoc[WidthSym](),
- }}>
- <FormattedTextBox
- {...OmitKeys(this.props, ['children']).omit}
- yPadding={10}
- xPadding={10}
- fieldKey={"text"}
- fontSize={12}
- dontRegisterView={true}
- noSidebar={true}
- dontScale={true}
- isContentActive={this.isContentActive}
- />
- </div>
- }
- </div>;
+ const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) =>
+ InteractionUtils.CreatePolyline(
+ inkData,
+ inkLeft,
+ inkTop,
+ highlightColor,
+ inkStrokeWidth,
+ fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0),
+ StrCast(this.layoutDoc.strokeLineJoin),
+ StrCast(this.layoutDoc.strokeLineCap),
+ StrCast(this.layoutDoc.strokeBezier),
+ !closed ? 'none' : fillColor === 'transparent' || suppressFill ? 'none' : fillColor,
+ startMarker,
+ endMarker,
+ markerScale,
+ undefined,
+ inkScaleX,
+ inkScaleY,
+ '',
+ this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? 'none' : 'visiblepainted'),
+ 0.0,
+ false,
+ downHdlr
+ );
+ const fsize = +StrCast(this.props.Document.fontSize, '12px').replace('px', '');
+ // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
+ // this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
+ // see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
+ const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
+ const interactions = {
+ onPointerLeave: action(() => (this._nearestScrPt = undefined)),
+ onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined,
+ onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
+ onContextMenu: () => {
+ const cm = ContextMenu.Instance;
+ !Doc.noviceMode && cm?.addItem({ description: 'Recognize Writing', event: this.analyzeStrokes, icon: 'paint-brush' });
+ cm?.addItem({ description: 'Toggle Mask', event: () => InkingStroke.toggleMask(this.rootDoc), icon: 'paint-brush' });
+ cm?.addItem({ description: 'Edit Points', event: action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)), icon: 'paint-brush' });
+ },
+ };
+ return (
+ <div className="inkStroke-wrapper">
+ <svg
+ className="inkStroke"
+ style={{
+ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+ cursor: this.props.isSelected() ? 'default' : undefined,
+ }}
+ {...(!closed ? interactions : {})}>
+ {closed ? inkLine : clickableLine(this.onPointerDown)}
+ {closed ? clickableLine(this.onPointerDown) : inkLine}
+ </svg>
+ {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? null : (
+ <div
+ className="inkStroke-text"
+ style={{
+ color: StrCast(this.layoutDoc.textColor, 'black'),
+ pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
+ width: this.layoutDoc[WidthSym](),
+ transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
+ transformOrigin: 'top left',
+ top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
+ }}>
+ <FormattedTextBox
+ {...OmitKeys(this.props, ['children']).omit}
+ setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
+ yPadding={10}
+ xPadding={10}
+ fieldKey={'text'}
+ fontSize={fsize}
+ dontRegisterView={true}
+ noSidebar={true}
+ dontScale={true}
+ isContentActive={this.isContentActive}
+ />
+ </div>
+ )}
+ {!closed ? null : (
+ <svg
+ className="inkStroke"
+ style={{
+ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+ cursor: this.props.isSelected() ? 'default' : undefined,
+ position: 'absolute',
+ }}
+ {...interactions}>
+ {clickableLine(this.onPointerDown, true)}
+ </svg>
+ )}
+ </div>
+ );
}
}
-
-export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
-export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
-export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
-export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
-export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
-export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
-export function SetActiveArrowScale(value: number) { ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); }
-export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
-export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
-export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
-export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
-export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); }
-export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
-export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); }
-export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
+export function SetActiveInkWidth(width: string): void {
+ !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+}
+export function SetActiveBezierApprox(bezier: string): void {
+ ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+}
+export function SetActiveInkColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
+}
+export function SetActiveFillColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+}
+export function SetActiveArrowStart(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+}
+export function SetActiveArrowEnd(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+}
+export function SetActiveArrowScale(value: number) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+}
+export function SetActiveDash(dash: string): void {
+ !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
+}
+export function ActiveInkPen(): Doc {
+ return Doc.UserDoc();
+}
+export function ActiveInkColor(): string {
+ return StrCast(ActiveInkPen()?.activeInkColor, 'black');
+}
+export function ActiveFillColor(): string {
+ return StrCast(ActiveInkPen()?.activeFillColor, '');
+}
+export function ActiveArrowStart(): string {
+ return StrCast(ActiveInkPen()?.activeArrowStart, '');
+}
+export function ActiveArrowEnd(): string {
+ return StrCast(ActiveInkPen()?.activeArrowEnd, '');
+}
+export function ActiveArrowScale(): number {
+ return NumCast(ActiveInkPen()?.activeArrowScale, 1);
+}
+export function ActiveDash(): string {
+ return StrCast(ActiveInkPen()?.activeDash, '0');
+}
+export function ActiveInkWidth(): number {
+ return Number(ActiveInkPen()?.activeInkWidth);
+}
+export function ActiveInkBezierApprox(): string {
+ return StrCast(ActiveInkPen()?.activeInkBezier);
+}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ec3bf6c18..99d50b4a2 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
-import "normalize.css";
+import 'normalize.css';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { Cast, NumCast, StrCast } from '../../fields/Types';
@@ -13,10 +13,9 @@ import { SelectionManager } from '../util/SelectionManager';
import { Transform } from '../util/Transform';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { TabDocView } from './collections/TabDocView';
-import "./LightboxView.scss";
+import './LightboxView.scss';
import { DocumentView } from './nodes/DocumentView';
import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
-import { CollectionMenu } from './collections/CollectionMenu';
interface LightboxViewProps {
PanelWidth: number;
@@ -26,19 +25,21 @@ interface LightboxViewProps {
@observer
export class LightboxView extends React.Component<LightboxViewProps> {
-
- @computed public static get LightboxDoc() { return this._doc; }
+ @computed public static get LightboxDoc() {
+ return this._doc;
+ }
private static LightboxDocTemplate = () => LightboxView._layoutTemplate;
@observable private static _layoutTemplate: Opt<Doc>;
@observable private static _doc: Opt<Doc>;
@observable private static _docTarget: Opt<Doc>;
@observable private static _docFilters: string[] = []; // filters
- @observable private static _tourMap: Opt<Doc[]> = []; // list of all tours available from the current target
- private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, scrollTop: Opt<number> }>;
- private static _history: Opt<{ doc: Doc, target?: Doc }[]> = [];
- private static _future: Opt<Doc[]> = [];
+ @observable private static _tourMap: Opt<Doc[]> = []; // list of all tours available from the current target
+ private static _savedState: Opt<{ panX: Opt<number>; panY: Opt<number>; scale: Opt<number>; scrollTop: Opt<number> }>;
+ private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
+ @observable private static _future: Opt<Doc[]> = [];
private static _docView: Opt<DocumentView>;
- static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, scrollTop: Opt<number> }> }[] = [];
+ private static openInTabFunc: any;
+ static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<{ panX: Opt<number>; panY: Opt<number>; scale: Opt<number>; scrollTop: Opt<number> }> }[] = [];
@action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc) {
if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
this.LightboxDoc._panX = this._savedState.panX;
@@ -51,50 +52,73 @@ export class LightboxView extends React.Component<LightboxViewProps> {
this._docFilters && (this._docFilters.length = 0);
this._future = this._history = [];
} else {
+ if (doc) {
+ const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+ l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen');
+ }
//TabDocView.PinDoc(doc, { hidePresBox: true });
- this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }];
+ this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
if (doc !== LightboxView.LightboxDoc) {
this._savedState = {
- panX: Cast(doc._panX, "number", null),
- panY: Cast(doc._panY, "number", null),
- scale: Cast(doc._viewScale, "number", null),
- scrollTop: Cast(doc._scrollTop, "number", null),
+ panX: Cast(doc._panX, 'number', null),
+ panY: Cast(doc._panY, 'number', null),
+ scale: Cast(doc._viewScale, 'number', null),
+ scrollTop: Cast(doc._scrollTop, 'number', null),
};
}
}
if (future) {
- this._future = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
+ this._future = [
+ ...(this._future ?? []),
+ ...(this.LightboxDoc ? [this.LightboxDoc] : []),
+ ...future
+ .slice()
+ .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
+ .sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length),
+ ];
}
this._doc = doc;
this._layoutTemplate = layoutTemplate;
this._docTarget = target || doc;
- this._tourMap = DocListCast(doc?.links).map(link => {
- const opp = LinkManager.getOppositeAnchor(link, doc!);
- return opp?.TourMap ? opp : undefined;
- }).filter(m => m).map(m => m!);
+ this._tourMap = DocListCast(doc?.links)
+ .map(link => {
+ const opp = LinkManager.getOppositeAnchor(link, doc!);
+ return opp?.TourMap ? opp : undefined;
+ })
+ .filter(m => m)
+ .map(m => m!);
return true;
}
- public static IsLightboxDocView(path: DocumentView[]) { return path.includes(this._docView!); }
- @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); }
- @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); }
+ public static IsLightboxDocView(path: DocumentView[]) {
+ return path.includes(this._docView!);
+ }
+ @computed get leftBorder() {
+ return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]);
+ }
+ @computed get topBorder() {
+ return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]);
+ }
lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2;
lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => {
- return <div className="lightboxView-navBtn-frame" style={{
- display: display(),
- left,
- width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
- bottom
- }}>
- <div className="lightboxView-navBtn" title={color} style={{ top, color: color ? "red" : "white", background: color ? "white" : undefined }}
- onClick={click}>
- <div style={{ height: 10 }}>{color}</div>
- <FontAwesomeIcon icon={icon as any} size="3x" />
+ return (
+ <div
+ className="lightboxView-navBtn-frame"
+ style={{
+ display: display(),
+ left,
+ width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+ bottom,
+ }}>
+ <div className="lightboxView-navBtn" title={color} style={{ top, color: color ? 'red' : 'white', background: color ? 'white' : undefined }} onClick={click}>
+ <div style={{ height: 10 }}>{color}</div>
+ <FontAwesomeIcon icon={icon as any} size="3x" />
+ </div>
</div>
- </div>;
- }
+ );
+ };
public static GetSavedState(doc: Doc) {
return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined;
}
@@ -103,26 +127,28 @@ export class LightboxView extends React.Component<LightboxViewProps> {
@action
public static SetCookie(cookie: string) {
if (this.LightboxDoc && cookie) {
- this._docFilters = (f => this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f])(`cookies:${cookie}:provide`);
+ this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`);
}
}
- public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc) => {
+ public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc, openInTabFunc?: any) => {
+ LightboxView.openInTabFunc = openInTabFunc;
SelectionManager.DeselectAll();
- return LightboxView.SetLightboxDoc(doc, undefined,
- [...DocListCast(doc[Doc.LayoutFieldKey(doc)]),
- ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]),
- ...(LightboxView._future ?? [])
- ].sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)), layoutTemplate);
- }
+ return LightboxView.SetLightboxDoc(
+ doc,
+ undefined,
+ [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '-annotations']), ...(LightboxView._future ?? [])].sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)),
+ layoutTemplate
+ );
+ };
docFilters = () => LightboxView._docFilters || [];
addDocTab = LightboxView.AddDocTab;
@action public static Next() {
const doc = LightboxView._doc!;
- const target = LightboxView._docTarget = LightboxView._future?.pop();
+ const target = (LightboxView._docTarget = this._future?.pop());
const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
if (targetDocView && target) {
const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement();
- l && (Cast(l.anchor2, Doc, null).backgroundColor = "lightgreen");
+ l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen');
targetDocView.focus(target, { originalTarget: target, willZoom: true, scale: 0.9 });
if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target });
} else {
@@ -147,10 +173,13 @@ export class LightboxView extends React.Component<LightboxViewProps> {
LightboxView.SetLightboxDoc(target);
}
}
- LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => {
- const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
- return opp?.TourMap ? opp : undefined;
- }).filter(m => m).map(m => m!);
+ LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links)
+ .map(link => {
+ const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
+ return opp?.TourMap ? opp : undefined;
+ })
+ .filter(m => m)
+ .map(m => m!);
}
@action public static Previous() {
@@ -163,20 +192,19 @@ export class LightboxView extends React.Component<LightboxViewProps> {
const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
if (docView) {
LightboxView._docTarget = target;
- const focusSpeed = 1000;
- doc._viewTransition = `transform ${focusSpeed}ms`;
if (!target) docView.ComponentView?.shrinkWrap?.();
else docView.focus(target, { willZoom: true, scale: 0.9 });
- setTimeout(() => doc._viewTransition = undefined, focusSpeed);
- }
- else {
+ } else {
LightboxView.SetLightboxDoc(doc, target);
}
if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc);
- LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => {
- const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
- return opp?.TourMap ? opp : undefined;
- }).filter(m => m).map(m => m!);
+ LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links)
+ .map(link => {
+ const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
+ return opp?.TourMap ? opp : undefined;
+ })
+ .filter(m => m)
+ .map(m => m!);
}
@action
stepInto = () => {
@@ -185,7 +213,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
target: LightboxView._docTarget,
future: LightboxView._future,
history: LightboxView._history,
- saved: LightboxView._savedState
+ saved: LightboxView._savedState,
});
const tours = LightboxView._tourMap;
if (tours && tours.length) {
@@ -195,49 +223,62 @@ export class LightboxView extends React.Component<LightboxViewProps> {
const coll = LightboxView._docTarget;
if (coll) {
const fieldKey = Doc.LayoutFieldKey(coll);
- LightboxView.SetLightboxDoc(coll, undefined, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
+ const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '-annotations'])];
+ const links = DocListCast(coll.links)
+ .map(link => LinkManager.getOppositeAnchor(link, coll))
+ .filter(doc => doc)
+ .map(doc => doc!);
+ LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links);
TabDocView.PinDoc(coll, { hidePresBox: true });
}
}
- }
+ };
future = () => LightboxView._future;
tourMap = () => LightboxView._tourMap;
- fitToBox = () => LightboxView._docTarget === LightboxView.LightboxDoc;
render() {
- let downx = 0, downy = 0;
- return !LightboxView.LightboxDoc ? (null) :
- <div className="lightboxView-frame"
- onPointerDown={e => { downx = e.clientX; downy = e.clientY; }}
+ let downx = 0,
+ downy = 0;
+ return !LightboxView.LightboxDoc ? null : (
+ <div
+ className="lightboxView-frame"
+ onPointerDown={e => {
+ downx = e.clientX;
+ downy = e.clientY;
+ }}
onClick={e => {
if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
LightboxView.SetLightboxDoc(undefined);
}
- }} >
-
- <div className="lightboxView-contents" style={{
- left: this.leftBorder,
- top: this.topBorder,
- width: this.lightboxWidth(),
- height: this.lightboxHeight(),
- clipPath: `path('${Doc.UserDoc().renderStyle === "comic" ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`
}}>
+ <div
+ className="lightboxView-contents"
+ style={{
+ left: this.leftBorder,
+ top: this.topBorder,
+ width: this.lightboxWidth(),
+ height: this.lightboxHeight(),
+ clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
+ }}>
{/* <CollectionMenu /> TODO:glr This is where it would go*/}
- <DocumentView ref={action((r: DocumentView | null) => {
- LightboxView._docView = r !== null ? r : undefined;
- r && setTimeout(action(() => {
- const target = LightboxView._docTarget;
- const doc = LightboxView._doc;
- const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
- if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
- else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true });
- }));
- })}
+ <DocumentView
+ ref={action((r: DocumentView | null) => {
+ LightboxView._docView = r !== null ? r : undefined;
+ r &&
+ setTimeout(
+ action(() => {
+ const target = LightboxView._docTarget;
+ const doc = LightboxView._doc;
+ const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+ if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
+ //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button
+ })
+ );
+ })}
Document={LightboxView.LightboxDoc}
DataDoc={undefined}
LayoutTemplate={LightboxView.LightboxDocTemplate}
addDocument={undefined}
- // fitContentsToDoc={this.fitToBox} // bcz: why do we want this? when we initially open a colletion, we shrinkwrap it which allows for user navigation. if we later encounter a collection, it's not clear to me that we want to make it either shrinkwrap or fitContents...
isDocumentActive={returnFalse}
isContentActive={returnTrue}
addDocTab={this.addDocTab}
@@ -247,7 +288,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
docFilters={this.docFilters}
removeDocument={undefined}
styleProvider={DefaultStyleProvider}
- layerProvider={returnTrue}
ScreenToLocalTransform={this.lightboxScreenToLocal}
PanelWidth={this.lightboxWidth}
PanelHeight={this.lightboxHeight}
@@ -258,34 +298,57 @@ export class LightboxView extends React.Component<LightboxViewProps> {
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- renderDepth={0} />
+ renderDepth={0}
+ />
</div>
- {this.navBtn(0, undefined, this.props.PanelHeight / 2 - 12.50, "chevron-left",
- () => LightboxView.LightboxDoc && LightboxView._history?.length ? "" : "none", e => {
+ {this.navBtn(
+ 0,
+ undefined,
+ this.props.PanelHeight / 2 - 12.5,
+ 'chevron-left',
+ () => (LightboxView.LightboxDoc && LightboxView._history?.length ? '' : 'none'),
+ e => {
e.stopPropagation();
LightboxView.Previous();
- })}
- {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), undefined, this.props.PanelHeight / 2 - 12.50, "chevron-right",
- () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => {
+ }
+ )}
+ {this.navBtn(
+ this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+ undefined,
+ this.props.PanelHeight / 2 - 12.5,
+ 'chevron-right',
+ () => (LightboxView.LightboxDoc && LightboxView._future?.length ? '' : 'none'),
+ e => {
e.stopPropagation();
LightboxView.Next();
- })}
+ },
+ this.future()?.length.toString()
+ )}
<LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} />
- <div className="lightboxView-tabBtn" title={"open in tab"}
+ <div
+ className="lightboxView-tabBtn"
+ title={'open in tab'}
onClick={e => {
e.stopPropagation();
- CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, "onRight");
+ CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, '');
+ //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace");
SelectionManager.DeselectAll();
LightboxView.SetLightboxDoc(undefined);
}}>
- <FontAwesomeIcon icon={"file-download"} size="2x" />
+ <FontAwesomeIcon icon={'file-download'} size="2x" />
</div>
- <div className="lightboxView-navBtn" title={"toggle fit width"}
- onClick={e => { e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; }}>
- <FontAwesomeIcon icon={"arrows-alt-h"} size="2x" />
+ <div
+ className="lightboxView-navBtn"
+ title={'toggle fit width'}
+ onClick={e => {
+ e.stopPropagation();
+ LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth;
+ }}>
+ <FontAwesomeIcon icon={LightboxView.LightboxDoc?._fitWidth ? 'arrows-alt-h' : 'arrows-alt-v'} size="2x" />
</div>
- </div>;
+ </div>
+ );
}
}
interface LightboxTourBtnProps {
@@ -297,12 +360,17 @@ interface LightboxTourBtnProps {
@observer
export class LightboxTourBtn extends React.Component<LightboxTourBtnProps> {
render() {
- return this.props.navBtn("50%", 0, 0, "chevron-down",
- () => LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? "" : "none", e => {
+ return this.props.navBtn(
+ '50%',
+ 0,
+ 0,
+ 'chevron-down',
+ () => (LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? '' : 'none'),
+ e => {
e.stopPropagation();
this.props.stepInto();
},
StrCast(this.props.tourMap()?.lastElement()?.TourMap)
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index c8e64b5c4..c7a7614ac 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,5 +1,5 @@
-@import "global/globalCssVariables";
-@import "nodeModuleOverrides";
+@import 'global/globalCssVariables';
+@import 'nodeModuleOverrides';
:root {
--flyoutHandleWidth: 28px;
@@ -24,7 +24,6 @@ body {
// -ms-user-select: none;
// }
-
.jsx-parser {
width: 100%;
height: 100%;
@@ -70,4 +69,4 @@ button:hover {
.svg-inline--fa {
vertical-align: unset;
-} \ No newline at end of file
+}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 8560ccb29..4cb1183d0 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -4,37 +4,45 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import { AssignAllExtensions } from "../../extensions/General/Extensions";
-import { Docs } from "../documents/Documents";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { LinkManager } from "../util/LinkManager";
-import { CollectionView } from "./collections/CollectionView";
-import { MainView } from "./MainView";
+import { AssignAllExtensions } from '../../extensions/General/Extensions';
+import { Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
+import { Docs } from '../documents/Documents';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { LinkManager } from '../util/LinkManager'; // this must come before importing Docs and CurrentUserUtils
+import { ReplayMovements } from '../util/ReplayMovements';
+import { TrackMovements } from '../util/TrackMovements';
+import { CollectionView } from './collections/CollectionView';
+import { MainView } from './MainView';
AssignAllExtensions();
(async () => {
- MainView.Live = window.location.search.includes("live");
- window.location.search.includes("safe") && CollectionView.SetSafeMode(true);
+ MainView.Live = window.location.search.includes('live');
+ window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
const info = await CurrentUserUtils.loadCurrentUser();
- if (info.id !== "__guest__") {
- // a guest will not have an id registered
- await CurrentUserUtils.loadUserDocument(info.id);
- } else {
- await Docs.Prototypes.initialize();
- }
- document.getElementById('root')!.addEventListener('wheel', event => {
- if (event.ctrlKey) {
- event.preventDefault();
- }
- }, true);
- const startload = (document as any).startLoad;
- const loading = Date.now() - (startload ? Number(startload) : (Date.now() - 3000));
- console.log("Load Time = " + loading);
- const d = new Date();
- d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000));
- const expires = "expires=" + d.toUTCString();
- document.cookie = `loadtime=${loading};${expires};path=/`;
- new LinkManager();
- ReactDOM.render(<MainView />, document.getElementById('root'));
-})(); \ No newline at end of file
+ if (info.email === 'guest') DocServer.Control.makeReadOnly();
+ await CurrentUserUtils.loadUserDocument(info.id);
+ setTimeout(() => {
+ document.getElementById('root')!.addEventListener(
+ 'wheel',
+ event => {
+ if (event.ctrlKey) {
+ event.preventDefault();
+ }
+ },
+ true
+ );
+ const startload = (document as any).startLoad;
+ const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
+ console.log('Loading Time = ' + loading);
+ const d = new Date();
+ d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
+ const expires = 'expires=' + d.toUTCString();
+ document.cookie = `loadtime=${loading};${expires};path=/`;
+ new LinkManager();
+ new TrackMovements();
+ new ReplayMovements();
+ ReactDOM.render(<MainView />, document.getElementById('root'));
+ }, 0);
+})();
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 15cd2c144..da79d2992 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,6 +1,5 @@
-@import "global/globalCssVariables";
-@import "nodeModuleOverrides";
-
+@import 'global/globalCssVariables';
+@import 'nodeModuleOverrides';
.dash-tooltip {
font-size: 11px;
@@ -20,6 +19,8 @@
position: relative;
width: 100%;
height: 100%;
+ display: flex;
+ flex-direction: column;
}
// add nodes menu. Note that the + button is actually an input label, not an actual button.
@@ -29,6 +30,11 @@
left: calc(100% + 5px);
z-index: 1;
pointer-events: none;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
}
.mainView-snapLines {
@@ -56,6 +62,17 @@
}
}
+.mainView-container,
+.mainView-container-Dark {
+ .lm_header .lm_tab {
+ padding: 0px;
+ opacity: 0.7;
+ box-shadow: none;
+ height: 25px;
+ border-bottom: black solid;
+ }
+}
+
.mainView-container {
color: $dark-gray;
@@ -116,7 +133,6 @@
position: absolute;
right: 0;
top: 0;
- z-index: 3000;
}
.mainView-propertiesDragger {
@@ -148,7 +164,8 @@
}
}
-.mainView-innerContent, .mainView-innerContent-Dark {
+.mainView-innerContent,
+.mainView-innerContent-Dark {
display: contents;
flex-direction: row;
position: relative;
@@ -175,14 +192,13 @@
background-color: $light-gray;
}
}
-
}
.mainView-libraryHandle {
background-color: $light-gray;
}
-.mainView-innerContent-Dark
-{
+
+.mainView-innerContent-Dark {
.propertiesView {
background-color: #252525;
@@ -208,6 +224,7 @@
background: #353535;
}
}
+
.mainView-container-Dark {
.contextMenu-cont {
background: $medium-gray;
@@ -243,7 +260,6 @@
}
.buttonContainer {
-
position: absolute;
bottom: 0;
@@ -261,8 +277,6 @@
}
}
-
-
.mainView-logout {
position: absolute;
right: 0;
@@ -300,11 +314,10 @@
}
.mainView-libraryFlyout-out {
- transition: width .25s;
+ transition: width 0.25s;
box-shadow: rgb(156, 147, 150) 0.2vw 0.2vw 0.2vw;
}
-
.mainView-libraryHandle {
width: var(--flyoutHandleWidth);
height: 55px;
@@ -325,7 +338,6 @@
margin-right: 3px;
padding-top: 19px;
}
-
}
.mainView-dashboard {
@@ -362,4 +374,4 @@
display: block;
width: 500px;
height: 1000px;
-} \ No newline at end of file
+}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8c0795881..e96f65548 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,22 +3,19 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, reaction } from 'mobx';
+import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { List } from '../../fields/List';
-import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
-import { PromiseValue, StrCast } from '../../fields/Types';
-import { TraceMobx } from '../../fields/util';
+import { StrCast } from '../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs, DocUtils } from '../documents/Documents';
+import { CollectionViewType } from '../documents/DocumentTypes';
import { CaptureManager } from '../util/CaptureManager';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
@@ -34,23 +31,24 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
import { CollectionLinearView } from './collections/collectionLinear';
import { CollectionMenu } from './collections/CollectionMenu';
-import { CollectionViewType } from './collections/CollectionView';
-import "./collections/TreeView.scss";
+import './collections/TreeView.scss';
import { ComponentDecorations } from './ComponentDecorations';
import { ContextMenu } from './ContextMenu';
+import { DashboardView } from './DashboardView';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import { GestureOverlay } from './GestureOverlay';
import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
import { Colors } from './global/globalEnums';
import { KeyManager } from './GlobalKeyHandler';
+import { InkTranscription } from './InkTranscription';
import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
-import "./MainView.scss";
+import './MainView.scss';
import { AudioBox } from './nodes/AudioBox';
-import { ButtonType } from './nodes/button/FontIconBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
+import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
@@ -64,7 +62,7 @@ import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
import { TopBar } from './topbar/TopBar';
-const _global = (window /* browser */ || global /* node */) as any;
+const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class MainView extends React.Component {
@@ -74,117 +72,393 @@ export class MainView extends React.Component {
@observable public LastButton: Opt<Doc>;
@observable private _windowWidth: number = 0;
@observable private _windowHeight: number = 0;
- @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
- @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
- @observable private _panelContent: string = "none";
- @observable private _sidebarContent: any = this.userDoc?.sidebar;
+ @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
+ @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
+ @observable private _panelContent: string = 'none';
+ @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel;
@observable private _leftMenuFlyoutWidth: number = 0;
+ @computed get _hideUI() {
+ return this.mainDoc && this.mainDoc._viewType !== CollectionViewType.Docking;
+ }
+
+ @computed private get dashboardTabHeight() {
+ return this._hideUI ? 0 : 27;
+ } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
+ @computed private get topOfDashUI() {
+ return this._hideUI ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
+ }
+ @computed private get topOfHeaderBarDoc() {
+ return this.topOfDashUI;
+ }
+ @computed private get topOfSidebarDoc() {
+ return this.topOfDashUI + this.topMenuHeight();
+ }
+ @computed private get topOfMainDoc() {
+ return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight();
+ }
+ @computed private get topOfMainDocContent() {
+ return this.topOfMainDoc + this.dashboardTabHeight;
+ }
+ @computed private get leftScreenOffsetOfMainDocView() {
+ return this.leftMenuWidth() - 2;
+ }
+ @computed private get userDoc() {
+ return Doc.UserDoc();
+ }
+ @computed private get colorScheme() {
+ return StrCast(Doc.ActiveDashboard?.colorScheme);
+ }
+ @observable mainDoc: Opt<Doc>;
+ @computed private get mainContainer() {
+ if (window.location.pathname.startsWith('/doc/')) {
+ DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
+ return this.mainDoc;
+ }
+ return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
+ }
+ @computed private get headerBarDoc() {
+ return Doc.MyHeaderBar;
+ }
+ @computed public get mainFreeform(): Opt<Doc> {
+ return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data));
+ }
- @computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
- @computed private get topOfDashUI() { return Number(DASHBOARD_SELECTOR_HEIGHT.replace("px", "")); }
- @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight(); }
- @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; }
- @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; }
- @computed private get userDoc() { return Doc.UserDoc(); }
- @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); }
- @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
- @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
-
- topMenuHeight = () => 35;
+ headerBarDocWidth = () => this.mainDocViewWidth();
+ headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
+ topMenuHeight = () => (this._hideUI ? 0 : 35);
topMenuWidth = returnZero; // value is ignored ...
- leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace("px", ""));
+ leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
leftMenuHeight = () => this._dashUIHeight;
leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
leftMenuFlyoutHeight = () => this._dashUIHeight;
- propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0));
+ propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.propertiesWidth || 0));
propertiesHeight = () => this._dashUIHeight;
- mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth();
- mainDocViewHeight = () => this._dashUIHeight;
+ mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
+ mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
componentDidMount() {
- document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
- const ele = document.getElementById("loader");
- const prog = document.getElementById("dash-progress");
+ document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!));
+ const ele = document.getElementById('loader');
+ const prog = document.getElementById('dash-progress');
if (ele && prog) {
// remove from DOM
setTimeout(() => {
clearTimeout();
- prog.style.transition = "1s";
- prog.style.width = "100%";
+ prog.style.transition = '1s';
+ prog.style.width = '100%';
}, 0);
- setTimeout(() => ele.outerHTML = '', 1000);
+ setTimeout(() => (ele.outerHTML = ''), 1000);
}
this._sidebarContent.proto = undefined;
if (!MainView.Live) {
- DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
- "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap",
- "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
+ DocServer.setPlaygroundFields([
+ 'dataTransition',
+ 'treeViewOpen',
+ 'showSidebar',
+ 'sidebarWidthPercent',
+ 'viewTransition',
+ 'panX',
+ 'panY',
+ 'fitWidth',
+ 'nativeWidth',
+ 'nativeHeight',
+ 'text-scrollHeight',
+ 'text-height',
+ 'hideMinimap',
+ 'viewScale',
+ 'scrollTop',
+ 'hidden',
+ 'curPage',
+ 'viewType',
+ 'chromeHidden',
+ 'width',
+ 'nativeWidth',
+ ]); // can play with these fields on someone else's
}
- DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
+ DocServer.GetRefField('rtfProto').then(
+ proto =>
+ proto instanceof Doc &&
+ reaction(
+ () => StrCast(proto.BROADCAST_MESSAGE),
+ msg => msg && alert(msg)
+ )
+ );
const tag = document.createElement('script');
- tag.src = "https://www.youtube.com/iframe_api";
+ tag.src = 'https://www.youtube.com/iframe_api';
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
- window.removeEventListener("keydown", KeyManager.Instance.handle);
- window.addEventListener("keydown", KeyManager.Instance.handle);
- window.removeEventListener("keyup", KeyManager.Instance.unhandle);
- window.addEventListener("keyup", KeyManager.Instance.unhandle);
- window.addEventListener("paste", KeyManager.Instance.paste as any);
- document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on
+ window.removeEventListener('keydown', KeyManager.Instance.handle);
+ window.addEventListener('keydown', KeyManager.Instance.handle);
+ window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+ window.addEventListener('keyup', KeyManager.Instance.unhandle);
+ window.addEventListener('paste', KeyManager.Instance.paste as any);
+ document.addEventListener('dash', (e: any) => {
+ // event used by chrome plugin to tell Dash which document to focus on
const id = FormattedTextBox.GetDocFromUrl(e.detail);
- DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined) : (null));
+ DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : null));
});
- document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
+ document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
this.initEventListeners();
}
componentWillUnMount() {
- window.removeEventListener("keyup", KeyManager.Instance.unhandle);
- window.removeEventListener("keydown", KeyManager.Instance.handle);
- window.removeEventListener("pointerdown", this.globalPointerDown);
- window.removeEventListener("paste", KeyManager.Instance.paste as any);
- document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener);
+ window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+ window.removeEventListener('keydown', KeyManager.Instance.handle);
+ window.removeEventListener('pointerdown', this.globalPointerDown, true);
+ window.removeEventListener('paste', KeyManager.Instance.paste as any);
+ document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
}
constructor(props: Readonly<{}>) {
super(props);
MainView.Instance = this;
- CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
+ DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
// causes errors to be generated when modifying an observable outside of an action
- configure({ enforceActions: "observed" });
+ configure({ enforceActions: 'observed' });
- if (window.location.pathname !== "/home") {
- const pathname = window.location.pathname.substr(1).split("/");
- if (pathname.length > 1 && pathname[0] === "doc") {
- CurrentUserUtils.MainDocId = pathname[1];
- !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
+ if (window.location.pathname !== '/home') {
+ const pathname = window.location.pathname.substr(1).split('/');
+ if (pathname.length > 1 && pathname[0] === 'doc') {
+ Doc.MainDocId = pathname[1];
+ //!this.userDoc &&
+ DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field)));
}
}
- library.add(...[fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar,
- fa.faSquare, far.faSquare as any, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
- fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
- fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
- fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
- fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
- fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp,
- fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
- fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
- fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV,
- fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
- fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
- fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
- fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any,
- fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle as any,
- fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer as any, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
- fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
- fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo,
- fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
- fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll,
- fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines,
- fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt]);
+ library.add(
+ ...[
+ fa.faEdit,
+ fa.faTrash,
+ fa.faTrashAlt,
+ fa.faShare,
+ fa.faDownload,
+ fa.faExpandArrowsAlt,
+ fa.faLayerGroup,
+ fa.faExternalLinkAlt,
+ fa.faCalendar,
+ fa.faSquare,
+ far.faSquare as any,
+ fa.faConciergeBell,
+ fa.faWindowRestore,
+ fa.faFolder,
+ fa.faMapPin,
+ fa.faMapMarker,
+ fa.faFingerprint,
+ fa.faCrosshairs,
+ fa.faDesktop,
+ fa.faUnlock,
+ fa.faLock,
+ fa.faLaptopCode,
+ fa.faMale,
+ fa.faCopy,
+ fa.faHandPointLeft,
+ fa.faHandPointRight,
+ fa.faCompass,
+ fa.faSnowflake,
+ fa.faMicrophone,
+ fa.faKeyboard,
+ fa.faQuestion,
+ fa.faTasks,
+ fa.faPalette,
+ fa.faAngleLeft,
+ fa.faAngleRight,
+ fa.faBell,
+ fa.faCamera,
+ fa.faExpand,
+ fa.faCaretDown,
+ fa.faCaretLeft,
+ fa.faCaretRight,
+ fa.faCaretSquareDown,
+ fa.faCaretSquareRight,
+ fa.faArrowsAltH,
+ fa.faPlus,
+ fa.faMinus,
+ fa.faTerminal,
+ fa.faToggleOn,
+ fa.faFile,
+ fa.faLocationArrow,
+ fa.faSearch,
+ fa.faFileDownload,
+ fa.faFileUpload,
+ fa.faStop,
+ fa.faCalculator,
+ fa.faWindowMaximize,
+ fa.faAddressCard,
+ fa.faQuestionCircle,
+ fa.faArrowLeft,
+ fa.faArrowRight,
+ fa.faArrowDown,
+ fa.faArrowUp,
+ fa.faBolt,
+ fa.faBullseye,
+ fa.faCaretUp,
+ fa.faCat,
+ fa.faCheck,
+ fa.faChevronRight,
+ fa.faChevronLeft,
+ fa.faChevronDown,
+ fa.faChevronUp,
+ fa.faClone,
+ fa.faCloudUploadAlt,
+ fa.faCommentAlt,
+ fa.faCompressArrowsAlt,
+ fa.faCut,
+ fa.faEllipsisV,
+ fa.faEraser,
+ fa.faExclamation,
+ fa.faFileAlt,
+ fa.faFileAudio,
+ fa.faFileVideo,
+ fa.faFilePdf,
+ fa.faFilm,
+ fa.faFilter,
+ fa.faFont,
+ fa.faGlobeAmericas,
+ fa.faGlobeAsia,
+ fa.faHighlighter,
+ fa.faLongArrowAltRight,
+ fa.faMousePointer,
+ fa.faMusic,
+ fa.faObjectGroup,
+ fa.faPause,
+ fa.faPen,
+ fa.faPenNib,
+ fa.faPhone,
+ fa.faPlay,
+ fa.faPortrait,
+ fa.faRedoAlt,
+ fa.faStamp,
+ fa.faStickyNote,
+ fa.faArrowsAltV,
+ fa.faTimesCircle,
+ fa.faThumbtack,
+ fa.faTree,
+ fa.faTv,
+ fa.faUndoAlt,
+ fa.faVideoSlash,
+ fa.faVideo,
+ fa.faAsterisk,
+ fa.faBrain,
+ fa.faImage,
+ fa.faPaintBrush,
+ fa.faTimes,
+ fa.faFlag,
+ fa.faEye,
+ fa.faArrowsAlt,
+ fa.faQuoteLeft,
+ fa.faSortAmountDown,
+ fa.faAlignLeft,
+ fa.faAlignCenter,
+ fa.faAlignRight,
+ fa.faHeading,
+ fa.faRulerCombined,
+ fa.faFillDrip,
+ fa.faLink,
+ fa.faUnlink,
+ fa.faBold,
+ fa.faItalic,
+ fa.faClipboard,
+ fa.faUnderline,
+ fa.faStrikethrough,
+ fa.faSuperscript,
+ fa.faSubscript,
+ fa.faIndent,
+ fa.faEyeDropper,
+ fa.faPaintRoller,
+ fa.faBars,
+ fa.faBrush,
+ fa.faShapes,
+ fa.faEllipsisH,
+ fa.faHandPaper,
+ fa.faMap,
+ fa.faUser,
+ faHireAHelper as any,
+ fa.faTrashRestore,
+ fa.faUsers,
+ fa.faWrench,
+ fa.faCog,
+ fa.faMap,
+ fa.faBellSlash,
+ fa.faExpandAlt,
+ fa.faArchive,
+ fa.faBezierCurve,
+ fa.faCircle,
+ far.faCircle as any,
+ fa.faLongArrowAltRight,
+ fa.faPenFancy,
+ fa.faAngleDoubleRight,
+ fa.faAngleDoubleDown,
+ fa.faAngleDoubleLeft,
+ fa.faAngleDoubleUp,
+ faBuffer as any,
+ fa.faExpand,
+ fa.faUndo,
+ fa.faSlidersH,
+ fa.faAngleUp,
+ fa.faAngleDown,
+ fa.faPlayCircle,
+ fa.faClock,
+ fa.faRocket,
+ fa.faExchangeAlt,
+ fa.faHashtag,
+ fa.faAlignJustify,
+ fa.faCheckSquare,
+ fa.faListUl,
+ fa.faWindowMinimize,
+ fa.faWindowRestore,
+ fa.faTextWidth,
+ fa.faTextHeight,
+ fa.faClosedCaptioning,
+ fa.faInfoCircle,
+ fa.faTag,
+ fa.faSyncAlt,
+ fa.faPhotoVideo,
+ fa.faArrowAltCircleDown,
+ fa.faArrowAltCircleUp,
+ fa.faArrowAltCircleLeft,
+ fa.faArrowAltCircleRight,
+ fa.faStopCircle,
+ fa.faCheckCircle,
+ fa.faGripVertical,
+ fa.faSortUp,
+ fa.faSortDown,
+ fa.faTable,
+ fa.faTh,
+ fa.faThList,
+ fa.faProjectDiagram,
+ fa.faSignature,
+ fa.faColumns,
+ fa.faChevronCircleUp,
+ fa.faUpload,
+ fa.faBorderAll,
+ fa.faBraille,
+ fa.faChalkboard,
+ fa.faPencilAlt,
+ fa.faEyeSlash,
+ fa.faSmile,
+ fa.faIndent,
+ fa.faOutdent,
+ fa.faChartBar,
+ fa.faBan,
+ fa.faPhoneSlash,
+ fa.faGripLines,
+ fa.faSave,
+ fa.faBookmark,
+ fa.faList,
+ fa.faListOl,
+ fa.faFolderPlus,
+ fa.faLightbulb,
+ fa.faBookOpen,
+ fa.faMapMarkerAlt,
+ fa.faSearchPlus,
+ fa.faVolumeUp,
+ fa.faVolumeDown,
+ fa.faSquareRootAlt,
+ fa.faVolumeMute,
+ ]
+ );
this.initAuthenticationRouters();
}
@@ -192,155 +466,218 @@ export class MainView extends React.Component {
AudioBox.Enabled = true;
const targets = document.elementsFromPoint(e.x, e.y);
if (targets.length) {
- const targClass = targets[0].className.toString();
- !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
- !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
+ let targClass = targets[0].className.toString();
+ for (let i = 0; i < targets.length - 1; i++) {
+ if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString();
+ else break;
+ }
+ !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu();
+ !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
}
});
initEventListeners = () => {
- window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
- window.addEventListener("dragover", e => e.preventDefault(), false);
+ window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
+ window.addEventListener('dragover', e => e.preventDefault(), false);
// document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
- document.addEventListener("pointerdown", this.globalPointerDown);
- document.addEventListener("click", (e: MouseEvent) => {
- if (!e.cancelBubble) {
- const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
- if (pathstr?.includes("libraryFlyout")) {
- SelectionManager.DeselectAll();
+ document.addEventListener('pointerdown', this.globalPointerDown, true);
+ document.addEventListener(
+ 'click',
+ (e: MouseEvent) => {
+ if (!e.cancelBubble) {
+ const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
+ if (pathstr?.includes('libraryFlyout')) {
+ SelectionManager.DeselectAll();
+ }
}
- }
- }, false);
+ },
+ false
+ );
document.oncontextmenu = () => false;
- }
+ };
initAuthenticationRouters = async () => {
- // Load the user's active dashboard, or create a new one if initial session after signup
- const received = CurrentUserUtils.MainDocId;
+ const received = Doc.MainDocId;
if (received && !this.userDoc) {
- reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true });
- } else {
- if (received && CurrentUserUtils._urlState.sharing) {
- reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
- initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
- if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
- CollectionDockingView.AddSplit(docField, "right");
- }
- }),
- );
- }
- const activeDash = PromiseValue(this.userDoc.activeDashboard);
- activeDash.then(dash => {
- if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash);
- else CurrentUserUtils.createNewDashboard(this.userDoc);
- });
+ reaction(
+ () => Doc.GuestTarget,
+ target => target && DashboardView.createNewDashboard(),
+ { fireImmediately: true }
+ );
}
- }
+ // else {
+ // PromiseValue(this.userDoc.activeDashboard).then(dash => {
+ // if (dash instanceof Doc) DashboardView.openDashboard(dash);
+ // else Doc.createNewDashboard();
+ // });
+ // }
+ };
@action
createNewPresentation = async () => {
- if (!await this.userDoc.myTrails) {
- this.userDoc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true
- }));
- }
- const pres = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
- CollectionDockingView.AddSplit(pres, "right");
- this.userDoc.activePresentation = pres;
- Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres);
- }
+ const pres = Docs.Create.PresDocument({ title: 'Untitled Trail', _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: 'alias', _chromeHidden: true, boxShadow: '0 0' });
+ CollectionDockingView.AddSplit(pres, 'left');
+ Doc.ActivePresentation = pres;
+ Doc.AddDocToList(Doc.MyTrails, 'data', pres);
+ };
@action
createNewFolder = async () => {
- if (!await this.userDoc.myFilesystem) {
- this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
- const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!;
- const newFolderButton: Doc = Docs.Create.FontIconDocument({
- onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder",
- btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true
- });
- this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.myFileOrphans as Doc], {
- title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
- treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
- treeViewTruncateTitleWidth: 150, ignoreClick: true,
- isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
- explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
- }));
- }
- const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
- Doc.AddDocToList(this.userDoc.myFilesystem as Doc, "data", folder);
- }
+ const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true });
+ Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
+ };
+ @observable _exploreMode = false;
+ @computed get exploreMode() {
+ return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
+ }
+ headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
+ mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
+ @computed get headerBarDocView() {
+ return (
+ <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
+ <DocumentView
+ key="headerBarDoc"
+ Document={this.headerBarDoc}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ docViewPath={returnEmptyDoclist}
+ styleProvider={DefaultStyleProvider}
+ rootSelected={returnTrue}
+ removeDocument={returnFalse}
+ fitContentsToBox={returnTrue}
+ isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
+ isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
+ ScreenToLocalTransform={this.headerBarScreenXf}
+ childHideResizeHandles={returnTrue}
+ dontRegisterView={true}
+ hideResizeHandles={true}
+ PanelWidth={this.headerBarDocWidth}
+ PanelHeight={this.headerBarDocHeight}
+ renderDepth={0}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
+ );
+ }
@computed get mainDocView() {
- return <DocumentView key="main"
- Document={this.mainContainer!}
- DataDoc={undefined}
- addDocument={undefined}
- addDocTab={this.addDocTabFunc}
- pinToPres={emptyFunction}
- docViewPath={returnEmptyDoclist}
- layerProvider={undefined}
- styleProvider={undefined}
- rootSelected={returnTrue}
- isContentActive={returnTrue}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={this.mainDocViewWidth}
- PanelHeight={this.mainDocViewHeight}
- focus={DocUtils.DefaultFocus}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- renderDepth={-1}
- />;
+ return (
+ <>
+ {this._hideUI ? null : this.headerBarDocView}
+ <DocumentView
+ key="main"
+ Document={this.mainContainer!}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ docViewPath={returnEmptyDoclist}
+ styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
+ rootSelected={returnTrue}
+ isContentActive={returnTrue}
+ removeDocument={undefined}
+ ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
+ PanelWidth={this.mainDocViewWidth}
+ PanelHeight={this.mainDocViewHeight}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ suppressSetHeight={true}
+ renderDepth={this._hideUI ? 0 : -1}
+ />
+ </>
+ );
}
@computed get dockingContent() {
- return <div key="docking" className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }}
- style={{
- minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
- transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined,
- }}>
- {!this.mainContainer ? (null) : this.mainDocView}
- </div>;
+ return (
+ <GestureOverlay>
+ <div
+ key="docking"
+ className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
+ onDrop={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ style={{
+ minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
+ transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined,
+ }}>
+ {!this.mainContainer ? null : this.mainDocView}
+ </div>
+ </GestureOverlay>
+ );
}
@action
onPropertiesPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e,
- action(e => (CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false),
- action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)),
- action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0), false);
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => ((SettingsManager.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
+ action(() => SettingsManager.propertiesWidth < 5 && (SettingsManager.propertiesWidth = 0)),
+ action(() => (SettingsManager.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
+ false
+ );
+ };
@action
onFlyoutPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e,
- action(e => (this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false),
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)),
() => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
- this.closeFlyout);
- }
+ this.closeFlyout
+ );
+ };
- sidebarScreenToLocal = () => new Transform(0, -this.topOfMainDoc, 1);
+ sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
- addDocTabFunc = (doc: Doc, where: string): boolean => {
- return where === "close" ? CollectionDockingView.CloseSplit(doc) :
- doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) : CollectionDockingView.AddSplit(doc, "right");
- }
-
+ addDocTabFunc = (doc: Doc, location: string): boolean => {
+ const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':');
+ const locationParams = locationFields.length > 1 ? locationFields[1] : '';
+ if (doc.dockingConfig) return DashboardView.openDashboard(doc);
+ switch (locationFields[0]) {
+ case 'dashboard':
+ return DashboardView.openDashboard(doc);
+ case 'close':
+ return CollectionDockingView.CloseSplit(doc, locationParams);
+ case 'fullScreen':
+ return CollectionDockingView.OpenFullScreen(doc);
+ case 'lightbox':
+ return LightboxView.AddDocTab(doc, location);
+ case 'toggle':
+ return CollectionDockingView.ToggleSplit(doc, locationParams);
+ case 'inPlace':
+ case 'add':
+ default:
+ return CollectionDockingView.AddSplit(doc, locationParams);
+ }
+ };
@computed get flyout() {
- return !this._leftMenuFlyoutWidth ? <div key="flyout" className={`mainView-libraryFlyout-out`}>
- {this.docButtons}
- </div> :
- <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }} >
- <div className="mainView-contentArea" >
+ return !this._leftMenuFlyoutWidth ? (
+ <div key="flyout" className={`mainView-libraryFlyout-out`}>
+ {this.docButtons}
+ </div>
+ ) : (
+ <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }}>
+ <div className="mainView-contentArea">
<DocumentView
Document={this._sidebarContent.proto || this._sidebarContent}
DataDoc={undefined}
@@ -348,8 +685,7 @@ export class MainView extends React.Component {
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
- layerProvider={undefined}
- styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards ? DashboardStyleProvider : DefaultStyleProvider}
+ styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
rootSelected={returnTrue}
removeDocument={returnFalse}
ScreenToLocalTransform={this.mainContainerXf}
@@ -369,38 +705,40 @@ export class MainView extends React.Component {
/>
</div>
{this.docButtons}
- </div>;
+ </div>
+ );
}
@computed get leftMenuPanel() {
- return <div key="menu" className="mainView-leftMenuPanel">
- <DocumentView
- Document={Doc.UserDoc().menuStack as Doc}
- DataDoc={undefined}
- addDocument={undefined}
- addDocTab={this.addDocTabFunc}
- pinToPres={emptyFunction}
- rootSelected={returnTrue}
- removeDocument={returnFalse}
- ScreenToLocalTransform={this.sidebarScreenToLocal}
- PanelWidth={this.leftMenuWidth}
- PanelHeight={this.leftMenuHeight}
- renderDepth={0}
- docViewPath={returnEmptyDoclist}
- focus={DocUtils.DefaultFocus}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- isContentActive={returnTrue}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- scriptContext={this}
- />
- </div>;
+ return (
+ <div key="menu" className="mainView-leftMenuPanel">
+ <DocumentView
+ Document={Doc.MyLeftSidebarMenu}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
+ PanelWidth={this.leftMenuWidth}
+ PanelHeight={this.leftMenuHeight}
+ renderDepth={0}
+ docViewPath={returnEmptyDoclist}
+ focus={DocUtils.DefaultFocus}
+ styleProvider={DefaultStyleProvider}
+ isContentActive={returnTrue}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ scriptContext={this}
+ />
+ </div>
+ );
}
@action
@@ -409,68 +747,80 @@ export class MainView extends React.Component {
const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
this.closeFlyout();
if (willOpen) {
- switch (this._panelContent = title) {
- case "Settings":
+ switch ((this._panelContent = title)) {
+ case 'Settings':
SettingsManager.Instance.open();
break;
- case "Help":
+ case 'Help':
break;
default:
this.expandFlyout(button);
}
}
return true;
- }
+ };
@computed get mainInnerContent() {
const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth();
const width = this.propertiesWidth() + leftMenuFlyoutWidth;
- return <>
- {this.leftMenuPanel}
- <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
- {this.flyout}
- <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
- <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
- </div>
- <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
-
- {this.dockingContent}
-
- <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
- <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
+ return (
+ <>
+ {this._hideUI ? null : this.leftMenuPanel}
+ <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
+ {this.flyout}
+ <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }} onPointerDown={this.onFlyoutPointerDown}>
+ <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? 'white' : 'black'} style={{ opacity: '50%' }} size="sm" />
</div>
- <div className="properties-container" style={{ width: this.propertiesWidth() }}>
- {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
+ <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
+ {this.dockingContent}
+
+ {this._hideUI ? null : (
+ <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
+ <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
+ </div>
+ )}
+ <div className="properties-container" style={{ width: this.propertiesWidth() }}>
+ {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
+ </div>
</div>
</div>
- </div>
- </>;
+ </>
+ );
}
@computed get mainDashboardArea() {
- return !this.userDoc ? (null) :
- <div className="mainView-dashboardArea" ref={r => {
- r && new _global.ResizeObserver(action(() => {
- this._dashUIWidth = r.getBoundingClientRect().width;
- this._dashUIHeight = r.getBoundingClientRect().height;
- })).observe(r);
- }} style={{
- color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black",
- height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
- width: "100%",
- }} >
+ return !this.userDoc ? null : (
+ <div
+ className="mainView-dashboardArea"
+ ref={r => {
+ r &&
+ new _global.ResizeObserver(
+ action(() => {
+ this._dashUIWidth = r.getBoundingClientRect().width;
+ this._dashUIHeight = r.getBoundingClientRect().height;
+ })
+ ).observe(r);
+ }}
+ style={{
+ color: this.colorScheme === ColorScheme.Dark ? 'rgb(205,205,205)' : 'black',
+ height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
+ width: '100%',
+ }}>
{this.mainInnerContent}
- </div>;
+ </div>
+ );
}
-
expandFlyout = action((button: Doc) => {
- // bcz: What's going on here!?
+ // bcz: What's going on here!?
// Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div
// generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
// interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
- this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250);
- setTimeout(action(() => this._leftMenuFlyoutWidth += 0.5), 0);
+ this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
+ setTimeout(
+ action(() => (this._leftMenuFlyoutWidth += 0.5)),
+ 0
+ );
this._sidebarContent.proto = button.target as any;
this.LastButton = button;
@@ -478,32 +828,31 @@ export class MainView extends React.Component {
closeFlyout = action(() => {
this.LastButton = undefined;
- this._panelContent = "none";
+ this._panelContent = 'none';
this._sidebarContent.proto = undefined;
this._leftMenuFlyoutWidth = 0;
});
- remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+ remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
- addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+ addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
return new Transform(-translateX, -translateY, 1 / scale);
- }
+ };
@computed get docButtons() {
- return !(this.userDoc.dockedBtns instanceof Doc) ? (null) :
- <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !this.userDoc.dockedBtns.linearViewIsExpanded ? "42px" : undefined }} >
+ return !Doc.MyDockedBtns ? null : (
+ <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !Doc.MyDockedBtns.linearViewIsExpanded ? '42px' : undefined }}>
<CollectionLinearView
- Document={this.userDoc.dockedBtns}
+ Document={Doc.MyDockedBtns}
DataDoc={undefined}
- fieldKey={"data"}
- dropAction={"alias"}
+ fieldKey={'data'}
+ dropAction={'alias'}
setHeight={returnFalse}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
@@ -527,58 +876,62 @@ export class MainView extends React.Component {
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- </div>;
+ ContainingCollectionDoc={undefined}
+ />
+ {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
+ </div>
+ );
}
@computed get snapLines() {
- return !this.userDoc.showSnapLines ? (null) : <div className="mainView-snapLines">
- <svg style={{ width: "100%", height: "100%" }}>
- {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
- {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
- </svg>
- </div>;
+ return !SnappingManager.GetShowSnapLines() ? null : (
+ <div className="mainView-snapLines">
+ <svg style={{ width: '100%', height: '100%' }}>
+ {SnappingManager.horizSnapLines().map(l => (
+ <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+ ))}
+ {SnappingManager.vertSnapLines().map(l => (
+ <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
+ ))}
+ </svg>
+ </div>
+ );
}
@computed get inkResources() {
- return <svg width={0} height={0}>
- <defs>
- <filter id="inkSelectionHalo">
- <feColorMatrix type="matrix"
- result="color"
- values="1 0 0 0 0
+ return (
+ <svg width={0} height={0}>
+ <defs>
+ <filter id="inkSelectionHalo">
+ <feColorMatrix
+ type="matrix"
+ result="color"
+ values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
- 0 0 0 1 0">
- </feColorMatrix>
- <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
- <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
- <feMerge>
- <feMergeNode in="bg"></feMergeNode>
- <feMergeNode in="offset"></feMergeNode>
- <feMergeNode in="SourceGraphic"></feMergeNode>
- </feMerge>
- </filter>
- </defs>
- </svg>;
- }
-
- @computed get topbar() {
- TraceMobx();
- return <div className="mainView-topbar">
- <TopBar />
- </div>;
+ 0 0 0 1 0"></feColorMatrix>
+ <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
+ <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
+ <feMerge>
+ <feMergeNode in="bg"></feMergeNode>
+ <feMergeNode in="offset"></feMergeNode>
+ <feMergeNode in="SourceGraphic"></feMergeNode>
+ </feMerge>
+ </filter>
+ </defs>
+ </svg>
+ );
}
- @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
- return !DocumentLinksButton.invisibleWebDoc ? null :
+ @computed get invisibleWebBox() {
+ // see note under the makeLink method in HypothesisUtils.ts
+ return !DocumentLinksButton.invisibleWebDoc ? null : (
<div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}>
<WebBox
- fieldKey={"data"}
+ fieldKey={'data'}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
Document={DocumentLinksButton.invisibleWebDoc}
- dropAction={"move"}
- layerProvider={undefined}
+ dropAction={'move'}
styleProvider={undefined}
isSelected={returnFalse}
select={returnFalse}
@@ -599,99 +952,131 @@ export class MainView extends React.Component {
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
/>
- </div>;
+ </div>
+ );
}
render() {
- return (<div className={`mainView-container${this.colorScheme}`}
- onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
- ref={r => {
- r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
- }}>
- {this.inkResources}
- <DictationOverlay />
- <SharingManager />
- <SettingsManager />
- <CaptureManager />
- <GroupManager />
- <GoogleAuthenticationManager />
- <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
- <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
- {this.topbar}
- {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
- {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
- {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
- <div style={{ position: "relative", display: LightboxView.LightboxDoc ? "none" : undefined, zIndex: 2001 }} >
- <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
+ return (
+ <div
+ className={`mainView-container${this.colorScheme}`}
+ onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
+ ref={r => {
+ r &&
+ new _global.ResizeObserver(
+ action(() => {
+ this._windowWidth = r.getBoundingClientRect().width;
+ this._windowHeight = r.getBoundingClientRect().height;
+ })
+ ).observe(r);
+ }}>
+ {this.inkResources}
+ <DictationOverlay />
+ <SharingManager />
+ <SettingsManager />
+ <CaptureManager />
+ <GroupManager />
+ <GoogleAuthenticationManager />
+ <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+ <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
+ {this._hideUI ? null : <TopBar />}
+ {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
+ {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
+ {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null}
+
+ {((page: string) => {
+ switch (page) {
+ case 'dashboard':
+ default:
+ return (
+ <>
+ <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
+ <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
+ </div>
+ {this.mainDashboardArea}
+ </>
+ );
+ case 'home':
+ return <DashboardView />;
+ }
+ })(Doc.ActivePage)}
+
+ <PreviewCursor />
+ <TaskCompletionBox />
+ <ContextMenu />
+ <RadialMenu />
+ <AnchorMenu />
+ <DashFieldViewMenu />
+ <MarqueeOptionsMenu />
+ <OverlayView />
+ <TimelineMenu />
+ <RichTextMenu />
+ <InkTranscription />
+ {this.snapLines}
+ <div className="mainView-webRef" ref={this.makeWebRef} />
+ <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
</div>
- <GestureOverlay >
- {this.mainDashboardArea}
- </GestureOverlay>
- <PreviewCursor />
- <TaskCompletionBox />
- <ContextMenu />
- <RadialMenu />
- <AnchorMenu />
- <MarqueeOptionsMenu />
- <OverlayView />
- <TimelineMenu />
- <RichTextMenu />
- {this.snapLines}
- <div className="mainView-webRef" ref={this.makeWebRef} />
- <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
- </div >);
+ );
}
makeWebRef = (ele: HTMLDivElement) => {
- reaction(() => DocumentLinksButton.invisibleWebDoc,
+ reaction(
+ () => DocumentLinksButton.invisibleWebDoc,
invisibleDoc => {
ReactDOM.unmountComponentAtNode(ele);
- invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" >
- <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}>
- <WebBox
- fieldKey={"data"}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- Document={invisibleDoc}
- dropAction={"move"}
- isSelected={returnFalse}
- docViewPath={returnEmptyDoclist}
- select={returnFalse}
- rootSelected={returnFalse}
- renderDepth={0}
- setHeight={returnFalse}
- layerProvider={undefined}
- styleProvider={undefined}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- bringToFront={returnFalse}
- isContentActive={emptyFunction}
- whenChildContentsActiveChanged={returnFalse}
- focus={returnFalse}
- PanelWidth={() => 500}
- PanelHeight={() => 800}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- />
- </div>;
- </span>, ele);
+ invisibleDoc &&
+ ReactDOM.render(
+ <span title="Drag as document" className="invisible-webbox">
+ <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}>
+ <WebBox
+ fieldKey={'data'}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ Document={invisibleDoc}
+ dropAction={'move'}
+ isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ select={returnFalse}
+ rootSelected={returnFalse}
+ renderDepth={0}
+ setHeight={returnFalse}
+ styleProvider={undefined}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ bringToFront={returnFalse}
+ isContentActive={emptyFunction}
+ whenChildContentsActiveChanged={returnFalse}
+ focus={returnFalse}
+ PanelWidth={() => 500}
+ PanelHeight={() => 800}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ />
+ </div>
+ ;
+ </span>,
+ ele
+ );
let success = false;
const onSuccess = () => {
success = true;
clearTimeout(interval);
- document.removeEventListener("editSuccess", onSuccess);
+ document.removeEventListener('editSuccess', onSuccess);
};
// For some reason, Hypothes.is annotations don't load until a click is registered on the page,
// so we keep simulating clicks until annotations have loaded and editing is successful
const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500);
setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
- document.addEventListener("editSuccess", onSuccess);
- });
- }
+ document.addEventListener('editSuccess', onSuccess);
+ }
+ );
+ };
}
-ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); }); \ No newline at end of file
+ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) {
+ MainView.Instance.selectMenu(doc);
+});
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 563261dec..b01ee5f42 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,21 +1,20 @@
-import { action, observable, ObservableMap, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt, AclSelfEdit } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { List } from "../../fields/List";
-import { NumCast } from "../../fields/Types";
-import { GetEffectiveAcl } from "../../fields/util";
-import { Utils } from "../../Utils";
-import { Docs } from "../documents/Documents";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { DragManager } from "../util/DragManager";
-import { undoBatch } from "../util/UndoManager";
-import "./MarqueeAnnotator.scss";
-import { DocumentView } from "./nodes/DocumentView";
-import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
-import { AnchorMenu } from "./pdf/AnchorMenu";
-import React = require("react");
-const _global = (window /* browser */ || global /* node */) as any;
+import { action, observable, ObservableMap, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, Opt } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { List } from '../../fields/List';
+import { NumCast } from '../../fields/Types';
+import { GetEffectiveAcl } from '../../fields/util';
+import { unimplementedFunction, Utils } from '../../Utils';
+import { Docs, DocUtils } from '../documents/Documents';
+import { DragManager } from '../util/DragManager';
+import { undoBatch } from '../util/UndoManager';
+import './MarqueeAnnotator.scss';
+import { DocumentView } from './nodes/DocumentView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { AnchorMenu } from './pdf/AnchorMenu';
+import React = require('react');
+const _global = (window /* browser */ || global) /* node */ as any;
export interface MarqueeAnnotatorProps {
rootDoc: Doc;
@@ -27,12 +26,13 @@ export interface MarqueeAnnotatorProps {
containerOffset?: () => number[];
mainCont: HTMLDivElement;
docView: DocumentView;
- savedAnnotations: ObservableMap<number, HTMLDivElement[]>;
+ savedAnnotations: () => ObservableMap<number, HTMLDivElement[]>;
annotationLayer: HTMLDivElement;
addDocument: (doc: Doc) => boolean;
getPageFromScroll?: (top: number) => number;
finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+ anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined;
}
@observer
export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
@@ -45,7 +45,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
@action
static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) {
- AnchorMenu.Instance.Status = "marquee";
+ AnchorMenu.Instance.Status = 'marquee';
AnchorMenu.Instance.fadeOut(true);
// clear out old marquees and initialize menu for new selection
Array.from(savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
@@ -59,29 +59,30 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
this._startY = this._top = (this.props.down[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
this._height = this._width = 0;
- const doc = (this.props.iframe?.()?.contentDocument ?? document);
- doc.addEventListener("pointermove", this.onSelectMove);
- doc.addEventListener("pointerup", this.onSelectEnd);
+ const doc = this.props.iframe?.()?.contentDocument ?? document;
+ doc.addEventListener('pointermove', this.onSelectMove);
+ doc.addEventListener('pointerup', this.onSelectEnd);
- AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
+ AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('rgba(173, 216, 230, 0.75)', true), true);
+ AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight('rgba(173, 216, 230, 0.75)', true));
AnchorMenu.Instance.Highlight = this.highlight;
- AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations);
+ AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations);
AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor;
/**
- * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
+ * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
*/
AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
e.preventDefault();
e.stopPropagation();
const sourceAnchorCreator = () => {
- const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color
+ const annoDoc = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color
annoDoc && this.props.addDocument(annoDoc);
return annoDoc;
};
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, "yellow");
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
FormattedTextBox.SelectOnLoad = target[Id];
return target;
};
@@ -90,57 +91,84 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
}
- }
+ },
});
});
+ /**
+ * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
+ * It also initiates a Drag/Drop interaction to place the text annotation.
+ */
+ AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop
+ ? unimplementedFunction
+ : action((e: PointerEvent, ele: HTMLElement) => {
+ e.preventDefault();
+ e.stopPropagation();
+ var cropRegion: Doc | undefined;
+ const sourceAnchorCreator = () => {
+ cropRegion = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color
+ cropRegion && this.props.addDocument(cropRegion);
+ return cropRegion;
+ };
+ const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+ dragComplete: e => {
+ if (!e.aborted && e.linkDocument) {
+ Doc.GetProto(e.linkDocument).linkRelationship = 'cropped image';
+ Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView.rootDoc.title;
+ }
+ },
+ });
+ });
}
componentWillUnmount() {
- const doc = (this.props.iframe?.()?.contentDocument ?? document);
- doc.removeEventListener("pointermove", this.onSelectMove);
- doc.removeEventListener("pointerup", this.onSelectEnd);
+ const doc = this.props.iframe?.()?.contentDocument ?? document;
+ doc.removeEventListener('pointermove', this.onSelectMove);
+ doc.removeEventListener('pointerup', this.onSelectEnd);
}
@undoBatch
@action
makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
- const savedAnnoMap = savedAnnotations ?? this.props.savedAnnotations;
+ const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
if (savedAnnoMap.size === 0) return undefined;
const savedAnnos = Array.from(savedAnnoMap.values())[0];
if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
const scale = this.props.scaling?.() || 1;
const anno = savedAnnos[0];
const containerOffset = this.props.containerOffset?.() || [0, 0];
- const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title });
- marqueeAnno.x = (parseInt(anno.style.left || "0") - containerOffset[0]) / scale;
- marqueeAnno.y = (parseInt(anno.style.top || "0") - containerOffset[1]) / scale + NumCast(this.props.scrollTop);
- marqueeAnno._height = parseInt(anno.style.height || "0") / scale;
- marqueeAnno._width = parseInt(anno.style.width || "0") / scale;
+ const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title });
+ marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1);
+ marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1) + NumCast(this.props.scrollTop);
+ marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1);
+ marqueeAnno._width = parseInt(anno.style.width || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1);
anno.remove();
savedAnnoMap.clear();
return marqueeAnno;
}
- const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: "transparent", title: "Selection on " + this.props.rootDoc.title });
+ const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title });
let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxY = -Number.MIN_VALUE;
const annoDocs: Doc[] = [];
- savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => value.map(anno => {
- const textRegion = new Doc();
- textRegion.x = parseInt(anno.style.left ?? "0");
- textRegion.y = parseInt(anno.style.top ?? "0");
- textRegion._height = parseInt(anno.style.height ?? "0");
- textRegion._width = parseInt(anno.style.width ?? "0");
- textRegion.annoTextRegion = textRegionAnno;
- textRegion.backgroundColor = color;
- annoDocs.push(textRegion);
- anno.remove();
- minY = Math.min(NumCast(textRegion.y), minY);
- minX = Math.min(NumCast(textRegion.x), minX);
- maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY);
- maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX);
- }));
+ savedAnnoMap.forEach((value: HTMLDivElement[], key: number) =>
+ value.map(anno => {
+ const textRegion = new Doc();
+ textRegion.x = parseInt(anno.style.left ?? '0');
+ textRegion.y = parseInt(anno.style.top ?? '0');
+ textRegion._height = parseInt(anno.style.height ?? '0');
+ textRegion._width = parseInt(anno.style.width ?? '0');
+ textRegion.annoTextRegion = textRegionAnno;
+ textRegion.backgroundColor = color;
+ annoDocs.push(textRegion);
+ anno.remove();
+ minY = Math.min(NumCast(textRegion.y), minY);
+ minX = Math.min(NumCast(textRegion.x), minX);
+ maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY);
+ maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX);
+ })
+ );
const textRegionAnnoProto = Doc.GetProto(textRegionAnno);
textRegionAnnoProto.y = Math.max(minY, 0);
@@ -151,29 +179,29 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs);
savedAnnoMap.clear();
return textRegionAnno;
- }
+ };
@action
highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => {
// creates annotation documents for current highlights
const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
!savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
- return annotationDoc as Doc ?? undefined;
- }
+ return (annotationDoc as Doc) ?? undefined;
+ };
public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
if (div.style.top) {
- div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString();
+ div.style.top = parseInt(div.style.top) /*+ this.getScrollFromPage(page)*/
+ .toString();
}
annotationLayer.append(div);
- div.style.backgroundColor = "#ACCEF7";
- div.style.opacity = "0.5";
+ div.style.backgroundColor = '#ACCEF7';
+ div.style.opacity = '0.5';
const savedPage = savedAnnotations.get(page);
if (savedPage) {
savedPage.push(div);
savedAnnotations.set(page, savedPage);
- }
- else {
+ } else {
savedAnnotations.set(page, [div]);
}
});
@@ -185,58 +213,65 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
const mainRect = this.props.mainCont.getBoundingClientRect();
const cliX = e.clientX * (this.props.iframeScaling?.() || 1) - boundingRect.left;
const cliY = e.clientY * (this.props.iframeScaling?.() || 1) - boundingRect.top;
- this._width = (cliX * (this.props.mainCont.offsetWidth / mainRect.width)) - this._startX;
- this._height = (cliY * (this.props.mainCont.offsetHeight / mainRect.height)) - this._startY + this.props.mainCont.scrollTop;
+ this._width = cliX * (this.props.mainCont.offsetWidth / mainRect.width) - this._startX;
+ this._height = cliY * (this.props.mainCont.offsetHeight / mainRect.height) - this._startY + this.props.mainCont.scrollTop;
this._left = Math.min(this._startX, this._startX + this._width);
this._top = Math.min(this._startY, this._startY + this._height);
this._width = Math.abs(this._width);
this._height = Math.abs(this._height);
e.stopPropagation();
- }
+ };
onSelectEnd = (e: PointerEvent) => {
const mainRect = this.props.mainCont.getBoundingClientRect();
const cliX = e.clientX * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.left : 0);
const cliY = e.clientY * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.top : 0);
- if (this._width > 10 || this._height > 10) { // configure and show the annotation/link menu if a the drag region is big enough
- const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox");
- if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though).
- const copy = document.createElement("div");
- ["border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]);
+ if (this._width > 10 || this._height > 10) {
+ // configure and show the annotation/link menu if a the drag region is big enough
+ const marquees = this.props.mainCont.getElementsByClassName('marqueeAnnotator-dragBox');
+ if (marquees?.length) {
+ // copy the temporary marquee to allow for multiple selections (not currently available though).
+ const copy = document.createElement('div');
+ ['border', 'opacity'].forEach(prop => (copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]));
const bounds = (marquees[0] as HTMLDivElement).getBoundingClientRect();
const uitls = Utils.GetScreenTransform(marquees[0] as HTMLDivElement);
- const rbounds = { top: uitls.translateY, left: uitls.translateX, width: (bounds.right - bounds.left), height: (bounds.bottom - bounds.top) };
+ const rbounds = { top: uitls.translateY, left: uitls.translateX, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top };
const otls = Utils.GetScreenTransform(this.props.annotationLayer);
const fbounds = { top: (rbounds.top - otls.translateY) / otls.scale, left: (rbounds.left - otls.translateX) / otls.scale, width: rbounds.width / otls.scale, height: rbounds.height / otls.scale };
- copy.style.top = fbounds.top.toString() + "px";
- copy.style.left = fbounds.left.toString() + "px";
- copy.style.width = fbounds.width.toString() + "px";
- copy.style.height = fbounds.height.toString() + "px";
- copy.className = "marqueeAnnotator-annotationBox";
+ copy.style.top = fbounds.top.toString() + 'px';
+ copy.style.left = fbounds.left.toString() + 'px';
+ copy.style.width = fbounds.width.toString() + 'px';
+ copy.style.height = fbounds.height.toString() + 'px';
+ copy.className = 'marqueeAnnotator-annotationBox';
(copy as any).marqueeing = true;
- MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
+ MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
}
AnchorMenu.Instance.jumpTo(cliX, cliY);
- if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
- this.highlight("rgba(245, 230, 95, 0.75)", false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color)
+ if (AnchorMenu.Instance.Highlighting) {
+ // when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
+ this.highlight('rgba(245, 230, 95, 0.75)', false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color)
}
this.props.finishMarquee(undefined, undefined, e);
} else {
- runInAction(() => this._width = this._height = 0);
+ runInAction(() => (this._width = this._height = 0));
this.props.finishMarquee(cliX, cliY, e);
}
- }
+ };
render() {
- return <div className="marqueeAnnotator-dragBox"
- style={{
- left: `${this._left}px`, top: `${this._top}px`,
- width: `${this._width}px`, height: `${this._height}px`,
- border: `${this._width === 0 ? "" : "2px dashed black"}`,
- opacity: 0.2
- }}>
- </div>;
+ return (
+ <div
+ className="marqueeAnnotator-dragBox"
+ style={{
+ left: `${this._left}px`,
+ top: `${this._top}px`,
+ width: `${this._width}px`,
+ height: `${this._height}px`,
+ border: `${this._width === 0 ? '' : '2px dashed black'}`,
+ opacity: 0.2,
+ }}></div>
+ );
}
}
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 555f4298d..033cdf1f7 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -1,3 +1,13 @@
+.overlayView {
+ position: absolute;
+ pointer-events: none;
+ top: 0;
+ width: 100vw;
+ height: 100vh;
+ /* background-color: pink; */
+ z-index: 100000;
+}
+
.overlayWindow-outerDiv {
border-radius: 5px;
overflow: hidden;
@@ -5,6 +15,7 @@
flex-direction: column;
top: 0;
left: 0;
+ pointer-events: all;
}
.overlayWindow-outerDiv,
@@ -46,4 +57,6 @@
.overlayView-doc {
z-index: 9002; //so that it appears above chroma
position: absolute;
+ top: 0;
+ left: 0;
} \ No newline at end of file
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 0f51cf9b2..5242fabb8 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,21 +1,21 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
+import * as React from 'react';
import ReactLoading from 'react-loading';
-import { Doc } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { Cast, NumCast } from "../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils } from "../../Utils";
-import { DocUtils } from "../documents/Documents";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { DragManager } from "../util/DragManager";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { Transform } from "../util/Transform";
-import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
-import { DocumentView } from "./nodes/DocumentView";
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { NumCast } from '../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
+import { DocUtils } from '../documents/Documents';
+import { DragManager } from '../util/DragManager';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { Transform } from '../util/Transform';
+import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView';
+import { DocumentView } from './nodes/DocumentView';
import './OverlayView.scss';
import { ScriptingRepl } from './ScriptingRepl';
-import { DefaultStyleProvider } from "./StyleProvider";
+import { DefaultStyleProvider } from './StyleProvider';
export type OverlayDisposer = () => void;
@@ -50,18 +50,18 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
}
onPointerDown = (_: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
+ document.addEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointerup', this.onPointerUp);
+ };
onResizerPointerDown = (_: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.onResizerPointerMove);
- document.removeEventListener("pointerup", this.onResizerPointerUp);
- document.addEventListener("pointermove", this.onResizerPointerMove);
- document.addEventListener("pointerup", this.onResizerPointerUp);
- }
+ document.removeEventListener('pointermove', this.onResizerPointerMove);
+ document.removeEventListener('pointerup', this.onResizerPointerUp);
+ document.addEventListener('pointermove', this.onResizerPointerMove);
+ document.addEventListener('pointerup', this.onResizerPointerUp);
+ };
@action
onPointerMove = (e: PointerEvent) => {
@@ -69,7 +69,7 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
this.x = Math.max(Math.min(this.x, window.innerWidth - this.width), 0);
this.y += e.movementY;
this.y = Math.max(Math.min(this.y, window.innerHeight - this.height), 0);
- }
+ };
@action
onResizerPointerMove = (e: PointerEvent) => {
@@ -77,28 +77,28 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
this.width = Math.max(this.width, 30);
this.height += e.movementY;
this.height = Math.max(this.height, 30);
- }
+ };
onPointerUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
+ };
onResizerPointerUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.onResizerPointerMove);
- document.removeEventListener("pointerup", this.onResizerPointerUp);
- }
+ document.removeEventListener('pointermove', this.onResizerPointerMove);
+ document.removeEventListener('pointerup', this.onResizerPointerUp);
+ };
render() {
return (
<div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
- <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown} >
- {this.props.overlayOptions.title || "Untitled"}
- <button onClick={this.props.onClick} className="overlayWindow-closeButton">X</button>
- </div>
- <div className="overlayWindow-content">
- {this.props.children}
+ <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}>
+ {this.props.overlayOptions.title || 'Untitled'}
+ <button onClick={this.props.onClick} className="overlayWindow-closeButton">
+ X
+ </button>
</div>
+ <div className="overlayWindow-content">{this.props.children}</div>
<div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown}></div>
</div>
);
@@ -124,13 +124,20 @@ export class OverlayView extends React.Component {
const index = this._elements.indexOf(ele);
if (index !== -1) this._elements.splice(index, 1);
});
- ele = <div key={Utils.GenerateGuid()} className="overlayView-wrapperDiv" style={{
- transform: `translate(${options.x}px, ${options.y}px)`,
- width: options.width,
- height: options.height,
- top: 0,
- left: 0
- }}>{ele}</div>;
+ ele = (
+ <div
+ key={Utils.GenerateGuid()}
+ className="overlayView-wrapperDiv"
+ style={{
+ transform: `translate(${options.x}px, ${options.y}px)`,
+ width: options.width,
+ height: options.height,
+ top: 0,
+ left: 0,
+ }}>
+ {ele}
+ </div>
+ );
this._elements.push(ele);
return remove;
}
@@ -141,15 +148,30 @@ export class OverlayView extends React.Component {
const index = this._elements.indexOf(contents);
if (index !== -1) this._elements.splice(index, 1);
});
- contents = <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>{contents}</OverlayWindow>;
+ contents = (
+ <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>
+ {contents}
+ </OverlayWindow>
+ );
this._elements.push(contents);
return remove;
}
+ removeOverlayDoc = (doc: Doc | Doc[]) => {
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc));
+ return true;
+ };
+
+ docScreenToLocalXf = computedFn(
+ function docScreenToLocalXf(this: any, doc: Doc) {
+ return () => new Transform(-NumCast(doc.x), -NumCast(doc.y), 1);
+ }.bind(this)
+ );
@computed get overlayDocs() {
- return CurrentUserUtils.OverlayDocs?.map(d => {
- let offsetx = 0, offsety = 0;
+ return DocListCast(Doc.MyOverlayDocs?.data).map(d => {
+ let offsetx = 0,
+ offsety = 0;
const dref = React.createRef<HTMLDivElement>();
const onPointerMove = action((e: PointerEvent, down: number[]) => {
if (e.buttons === 1) {
@@ -159,10 +181,10 @@ export class OverlayView extends React.Component {
if (e.metaKey) {
const dragData = new DragManager.DocumentDragData([d]);
dragData.offset = [-offsetx, -offsety];
- dragData.dropAction = "move";
+ dragData.dropAction = 'move';
dragData.removeDocument = (doc: Doc | Doc[]) => {
- const docs = (doc instanceof Doc) ? [doc] : doc;
- docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocs, Doc, null), "data", d));
+ const docs = doc instanceof Doc ? [doc] : doc;
+ docs.forEach(d => Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, d));
return true;
};
dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
@@ -179,32 +201,39 @@ export class OverlayView extends React.Component {
offsetx = NumCast(d.x) - e.clientX;
offsety = NumCast(d.y) - e.clientY;
};
- return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ top: d.type === 'presentation' ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
- <DocumentView
- Document={d}
- rootSelected={returnTrue}
- bringToFront={emptyFunction}
- addDocument={undefined}
- removeDocument={undefined}
- PanelWidth={returnOne}
- PanelHeight={returnOne}
- ScreenToLocalTransform={Transform.Identity}
- renderDepth={1}
- isDocumentActive={returnTrue}
- isContentActive={emptyFunction}
- whenChildContentsActiveChanged={emptyFunction}
- focus={DocUtils.DefaultFocus}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- docViewPath={returnEmptyDoclist}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- </div>;
+ return (
+ <div
+ className="overlayView-doc"
+ ref={dref}
+ key={d[Id]}
+ onPointerDown={onPointerDown}
+ style={{ top: d.type === 'presentation' ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
+ <DocumentView
+ Document={d}
+ rootSelected={returnTrue}
+ bringToFront={emptyFunction}
+ addDocument={undefined}
+ removeDocument={this.removeOverlayDoc}
+ PanelWidth={d[WidthSym]}
+ PanelHeight={d[HeightSym]}
+ ScreenToLocalTransform={this.docScreenToLocalXf(d)}
+ renderDepth={1}
+ isDocumentActive={returnTrue}
+ isContentActive={returnTrue}
+ whenChildContentsActiveChanged={emptyFunction}
+ focus={DocUtils.DefaultFocus}
+ styleProvider={DefaultStyleProvider}
+ docViewPath={returnEmptyDoclist}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
+ );
});
}
@@ -212,13 +241,10 @@ export class OverlayView extends React.Component {
return OverlayView.Instance.addElement(<ReactLoading type="spinningBubbles" color="green" height={250} width={250} />, { x: 300, y: 200 });
}
-
render() {
return (
<div className="overlayView" id="overlayView">
- <div>
- {this._elements}
- </div>
+ <div>{this._elements}</div>
<CollectionFreeFormLinksView key="freeformLinks" />
{this.overlayDocs}
</div>
@@ -228,4 +254,4 @@ export class OverlayView extends React.Component {
// bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error
ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) {
OverlayView.Instance.addWindow(<ScriptingRepl />, options);
-}); \ No newline at end of file
+});
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 529697f71..954529bc9 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -54,7 +54,6 @@ export default class Palette extends React.Component<PaletteProps> {
focus={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={returnEmptyString}
- layerProvider={undefined}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ef1360ef1..68f5f072d 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,17 +1,16 @@
import { action, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import "normalize.css";
+import 'normalize.css';
import * as React from 'react';
import { Doc } from '../../fields/Doc';
import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { returnFalse } from '../../Utils';
import { DocServer } from '../DocServer';
import { Docs, DocUtils } from '../documents/Documents';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { Transform } from "../util/Transform";
+import { Transform } from '../util/Transform';
import { undoBatch, UndoManager } from '../util/UndoManager';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import "./PreviewCursor.scss";
-import { returnFalse } from '../../Utils';
+import './PreviewCursor.scss';
@observer
export class PreviewCursor extends React.Component<{}> {
@@ -24,47 +23,66 @@ export class PreviewCursor extends React.Component<{}> {
@observable public static Visible = false;
constructor(props: any) {
super(props);
- document.addEventListener("keydown", this.onKeyPress);
- document.addEventListener("paste", this.paste);
+ document.addEventListener('keydown', this.onKeyPress);
+ document.addEventListener('paste', this.paste);
}
paste = async (e: ClipboardEvent) => {
if (PreviewCursor.Visible && e.clipboardData) {
const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
- runInAction(() => PreviewCursor.Visible = false);
+ runInAction(() => (PreviewCursor.Visible = false));
// tests for URL and makes web document
const re: any = /^https?:\/\//g;
- const plain = e.clipboardData.getData("text/plain");
+ const plain = e.clipboardData.getData('text/plain');
if (plain) {
// tests for youtube and makes video document
- if (plain.indexOf("www.youtube.com/watch") !== -1) {
- const url = plain.replace("youtube.com/watch?v=", "youtube.com/embed/");
- undoBatch(() => PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
- title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5,
- x: newPoint[0], y: newPoint[1]
- })))();
- }
-
- else if (re.test(plain)) {
+ if (plain.indexOf('www.youtube.com/watch') !== -1) {
+ const url = plain.replace('youtube.com/watch?v=', 'youtube.com/embed/');
+ undoBatch(() =>
+ PreviewCursor._addDocument(
+ Docs.Create.VideoDocument(url, {
+ title: url,
+ _width: 400,
+ _height: 315,
+ _nativeWidth: 600,
+ _nativeHeight: 472.5,
+ x: newPoint[0],
+ y: newPoint[1],
+ })
+ )
+ )();
+ } else if (re.test(plain)) {
const url = plain;
- undoBatch(() => PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
- title: url, _width: 500, _height: 300, useCors: true, x: newPoint[0], y: newPoint[1]
- })))();
- }
- else if (plain.startsWith("__DashDocId(") || plain.startsWith("__DashCloneId(")) {
- const clone = plain.startsWith("__DashCloneId(");
- const docids = plain.split(":");
- const strs = docids[0].split(",");
- const ptx = Number(strs[0].substring((clone ? "__DashCloneId(" : "__DashDocId(").length));
+ undoBatch(() =>
+ PreviewCursor._addDocument(
+ Docs.Create.WebDocument(url, {
+ title: url,
+ _width: 500,
+ _height: 300,
+ useCors: true,
+ x: newPoint[0],
+ y: newPoint[1],
+ })
+ )
+ )();
+ } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
+ const clone = plain.startsWith('__DashCloneId(');
+ const docids = plain.split(':');
+ const strs = docids[0].split(',');
+ const ptx = Number(strs[0].substring((clone ? '__DashCloneId(' : '__DashDocId(').length));
const pty = Number(strs[1].substring(0, strs[1].length - 1));
- const batch = UndoManager.StartBatch("cloning");
+ const batch = UndoManager.StartBatch('cloning');
{
- const docs = await Promise.all(docids.filter((did, i) => i).map(async (did) => {
- const doc = Cast(await DocServer.GetRefField(did), Doc, null);
- return clone ? (await Doc.MakeClone(doc)).clone : doc;
- }));
+ const docs = await Promise.all(
+ docids
+ .filter((did, i) => i)
+ .map(async did => {
+ const doc = Cast(await DocServer.GetRefField(did), Doc, null);
+ return clone ? (await Doc.MakeClone(doc)).clone : doc;
+ })
+ );
const firstx = docs.length ? NumCast(docs[0].x) + ptx - newPoint[0] : 0;
const firsty = docs.length ? NumCast(docs[0].y) + pty - newPoint[1] : 0;
docs.map(doc => {
@@ -75,79 +93,99 @@ export class PreviewCursor extends React.Component<{}> {
}
batch.end();
e.stopPropagation();
- }
- else {
+ } else {
// creates text document
FormattedTextBox.PasteOnLoad = e;
- UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(CurrentUserUtils.GetNewTextDoc("-pasted text-", newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), "paste");
+ UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(DocUtils.GetNewTextDoc('-pasted text-', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
}
- } else
- //pasting in images
- if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes("<img src=")) {
- const re: any = /<img src="(.*?)"/g;
- const arr: any[] = re.exec(e.clipboardData.getData("text/html"));
+ }
+ //pasting in images
+ else if (e.clipboardData.getData('text/html') !== '' && e.clipboardData.getData('text/html').includes('<img src=')) {
+ const re: any = /<img src="(.*?)"/g;
+ const arr: any[] = re.exec(e.clipboardData.getData('text/html'));
- undoBatch(() => PreviewCursor._addDocument(Docs.Create.ImageDocument(
- arr[1], {
- _width: 300, title: arr[1],
- x: newPoint[0],
- y: newPoint[1],
- })))();
- } else if (e.clipboardData.items.length) {
- const batch = UndoManager.StartBatch("collection view drop");
- const files: File[] = [];
- Array.from(e.clipboardData.items).forEach(item => {
- const file = item.getAsFile();
- file && files.push(file);
- });
- const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
- generatedDocuments.forEach(PreviewCursor._addDocument);
- batch.end();
- }
+ undoBatch(() =>
+ PreviewCursor._addDocument(
+ Docs.Create.ImageDocument(arr[1], {
+ _width: 300,
+ title: arr[1],
+ x: newPoint[0],
+ y: newPoint[1],
+ })
+ )
+ )();
+ } else if (e.clipboardData.items.length) {
+ const batch = UndoManager.StartBatch('collection view drop');
+ const files: File[] = [];
+ Array.from(e.clipboardData.items).forEach(item => {
+ const file = item.getAsFile();
+ file && files.push(file);
+ });
+ const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
+ generatedDocuments.forEach(PreviewCursor._addDocument);
+ batch.end();
+ }
}
- }
+ };
@action
onKeyPress = (e: KeyboardEvent) => {
- // Mixing events between React and Native is finicky.
+ // Mixing events between React and Native is finicky.
//if not these keys, make a textbox if preview cursor is active!
- if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" &&
- e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" &&
- e.key !== "Insert" && e.key !== "Home" && e.key !== "End" && e.key !== "PageUp" && e.key !== "PageDown" &&
- e.key !== "NumLock" && e.key !== " " &&
+ if (
+ e.key !== 'Escape' &&
+ e.key !== 'Backspace' &&
+ e.key !== 'Delete' &&
+ e.key !== 'CapsLock' &&
+ e.key !== 'Alt' &&
+ e.key !== 'Shift' &&
+ e.key !== 'Meta' &&
+ e.key !== 'Control' &&
+ e.key !== 'Insert' &&
+ e.key !== 'Home' &&
+ e.key !== 'End' &&
+ e.key !== 'PageUp' &&
+ e.key !== 'PageDown' &&
+ e.key !== 'NumLock' &&
+ e.key !== ' ' &&
(e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
- (e.keyCode < 173 || e.keyCode > 183 || e.key === "-") && // mute, volume up/down etc, - is there specifically because its keycode is 173 in Firefox so shouldn't be avoided
- !e.key.startsWith("Arrow") &&
- !e.defaultPrevented) {
- if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
+ (e.keyCode < 173 || e.keyCode > 183 || e.key === '-') && // mute, volume up/down etc, - is there specifically because its keycode is 173 in Firefox so shouldn't be avoided
+ !e.key.startsWith('Arrow') &&
+ !e.defaultPrevented
+ ) {
+ if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {
+ // /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e);
- ((!e.ctrlKey && !e.metaKey) || e.key !== "v") && (PreviewCursor.Visible = false);
+ ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (PreviewCursor.Visible = false);
}
} else if (PreviewCursor.Visible) {
- if (e.key === "ArrowRight") {
+ if (e.key === 'ArrowRight') {
PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
- } else if (e.key === "ArrowLeft") {
+ } else if (e.key === 'ArrowLeft') {
PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
- } else if (e.key === "ArrowUp") {
+ } else if (e.key === 'ArrowUp') {
PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
- } else if (e.key === "ArrowDown") {
+ } else if (e.key === 'ArrowDown') {
PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
}
}
- }
+ };
//when focus is lost, this will remove the preview cursor
@action onBlur = (): void => {
PreviewCursor.Visible = false;
- }
+ };
@action
- public static Show(x: number, y: number,
+ public static Show(
+ x: number,
+ y: number,
onKeyPress: (e: KeyboardEvent) => void,
addLiveText: (doc: Doc) => void,
getTransform: () => Transform,
addDocument: undefined | ((doc: Doc | Doc[]) => boolean),
- nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean)) {
+ nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean)
+ ) {
this._clickPoint = [x, y];
this._onKeyPress = onKeyPress;
this._addLiveTextDoc = addLiveText;
@@ -157,10 +195,10 @@ export class PreviewCursor extends React.Component<{}> {
this.Visible = true;
}
render() {
- return (!PreviewCursor._clickPoint || !PreviewCursor.Visible) ? (null) :
- <div className={`previewCursor${StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme)}`} onBlur={this.onBlur} tabIndex={0} ref={e => e?.focus()}
- style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}>
+ return !PreviewCursor._clickPoint || !PreviewCursor.Visible ? null : (
+ <div className={`previewCursor${StrCast(Doc.ActiveDashboard?.colorScheme)}`} onBlur={this.onBlur} tabIndex={0} ref={e => e?.focus()} style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}>
I
- </div >;
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 862c37890..39e7b89c1 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,223 +1,345 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Opt } from "../../fields/Doc";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Opt } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
import { RichTextField } from '../../fields/RichTextField';
-import { BoolCast, StrCast } from "../../fields/Types";
+import { BoolCast, StrCast } from '../../fields/Types';
+import { ImageField } from '../../fields/URLField';
import { DocUtils } from '../documents/Documents';
-import { DocumentType } from '../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { SelectionManager } from '../util/SelectionManager';
import { undoBatch } from '../util/UndoManager';
-import { CollectionViewType } from './collections/CollectionView';
+import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { DocumentView } from './nodes/DocumentView';
+import { VideoBox } from './nodes/VideoBox';
+import { pasteImageBitmap } from './nodes/WebBoxRenderer';
import './PropertiesButtons.scss';
-import React = require("react");
-import { Colors } from "./global/globalEnums";
-import { CollectionFreeFormView } from "./collections/collectionFreeForm";
-import { DocumentManager } from "../util/DocumentManager";
-import { pasteImageBitmap } from "./nodes/WebBoxRenderer";
-import { VideoBox } from "./nodes/VideoBox";
-import { Id } from "../../fields/FieldSymbols";
-import { ImageField } from "../../fields/URLField";
-const higflyout = require("@hig/flyout");
+import React = require('react');
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
enum UtilityButtonState {
Default,
OpenRight,
- OpenExternally
+ OpenExternally,
}
@observer
export class PropertiesButtons extends React.Component<{}, {}> {
@observable public static Instance: PropertiesButtons;
- @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc; }
- @computed get selectedTabView() { return !SelectionManager.SelectedSchemaDoc() && SelectionManager.Views().lastElement()?.topMost; }
+ @computed get selectedDoc() {
+ return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc;
+ }
+ @computed get selectedTabView() {
+ return !SelectionManager.SelectedSchemaDoc() && SelectionManager.Views().lastElement()?.topMost;
+ }
propertyToggleBtn = (label: string, property: string, tooltip: (on?: any) => string, icon: (on: boolean) => string, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => {
const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedDoc;
- const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => (dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true;
- return !targetDoc ? (null) :
+ const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true);
+ return !targetDoc ? null : (
<Tooltip title={<div className={`dash-tooltip`}>{tooltip(targetDoc?.[property])} </div>} placement="top">
<div>
- <div className={`propertiesButtons-linkButton-empty toggle-${StrCast(targetDoc[property]).includes(":hover") ? "hover" : targetDoc[property] ? "on" : "off"}`}
+ <div
+ className={`propertiesButtons-linkButton-empty toggle-${StrCast(targetDoc[property]).includes(':hover') ? 'hover' : targetDoc[property] ? 'on' : 'off'}`}
onPointerDown={e => e.stopPropagation()}
onClick={undoBatch(() => {
if (SelectionManager.Views().length > 1) {
SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property));
} else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
- })} >
+ })}>
<FontAwesomeIcon className="documentdecorations-icon" size="lg" icon={icon(BoolCast(targetDoc?.[property])) as any} />
</div>
<div className="propertiesButtons-title">{label}</div>
</div>
- </Tooltip>;
- }
+ </Tooltip>
+ );
+ };
@computed get lockButton() {
- return this.propertyToggleBtn("No\xA0Drag", "_lockedPosition", on => `${on ? "Unlock" : "Lock"} position to prevent dragging`, on => "thumbtack");
+ return this.propertyToggleBtn(
+ 'No\xA0Drag',
+ '_lockedPosition',
+ on => `${on ? 'Unlock' : 'Lock'} position to prevent dragging`,
+ on => 'thumbtack'
+ );
}
@computed get dictationButton() {
- return this.propertyToggleBtn("Dictate", "_showAudio", on => `${on ? "Hide" : "Show"} dictation/recording controls`, on => "microphone");
+ return this.propertyToggleBtn(
+ 'Dictate',
+ '_showAudio',
+ on => `${on ? 'Hide' : 'Show'} dictation/recording controls`,
+ on => 'microphone'
+ );
}
@computed get maskButton() {
- return this.propertyToggleBtn("Mask", "isInkMask", on => on ? "Make plain ink" : "Make highlight mask", on => "paint-brush", (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc));
+ return this.propertyToggleBtn(
+ 'Mask',
+ 'isInkMask',
+ on => (on ? 'Make plain ink' : 'Make highlight mask'),
+ on => 'paint-brush',
+ (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc)
+ );
}
@computed get clustersButton() {
- return this.propertyToggleBtn("Clusters", "_useClusters", on => `${on ? "Hide" : "Show"} clusters`, on => "braille");
+ return this.propertyToggleBtn(
+ 'Clusters',
+ '_useClusters',
+ on => `${on ? 'Hide' : 'Show'} clusters`,
+ on => 'braille'
+ );
}
@computed get panButton() {
- return this.propertyToggleBtn("Lock\xA0View", "_lockedTransform", on => `${on ? "Unlock" : "Lock"} panning of view`, on => "lock");
+ return this.propertyToggleBtn(
+ 'Lock\xA0View',
+ '_lockedTransform',
+ on => `${on ? 'Unlock' : 'Lock'} panning of view`,
+ on => 'lock'
+ );
}
@computed get fitContentButton() {
- return this.propertyToggleBtn("View All", "_fitToBox", on => `${on ? "Don't" : "Do"} fit content to container visible area`, on => "eye");
+ return this.propertyToggleBtn(
+ 'View All',
+ '_fitContentsToBox',
+ on => `${on ? "Don't" : 'Do'} fit content to container visible area`,
+ on => 'eye'
+ );
}
@computed get fitWidthButton() {
- return this.propertyToggleBtn("Fit\xA0Width", "_fitWidth", on => `${on ? "Don't" : "Do"} fit content to width of container`, on => "arrows-alt-h");
+ return this.propertyToggleBtn(
+ 'Fit\xA0Width',
+ '_fitWidth',
+ on => `${on ? "Don't" : 'Do'} fit content to width of container`,
+ on => 'arrows-alt-h'
+ );
}
@computed get captionButton() {
- return this.propertyToggleBtn("Caption", "_showCaption", on => `${on ? "Hide" : "Show"} caption footer`, on => "closed-captioning", (dv, doc) => (dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? "caption" : undefined);
+ return this.propertyToggleBtn(
+ 'Caption',
+ '_showCaption',
+ on => `${on ? 'Hide' : 'Show'} caption footer`,
+ on => 'closed-captioning',
+ (dv, doc) => ((dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? 'caption' : undefined)
+ );
}
@computed get chromeButton() {
- return this.propertyToggleBtn("Controls", "_chromeHidden", on => `${on ? "Show" : "Hide"} editing UI`, on => "edit", (dv, doc) => (dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden);
+ return this.propertyToggleBtn(
+ 'Controls',
+ '_chromeHidden',
+ on => `${on ? 'Show' : 'Hide'} editing UI`,
+ on => 'edit',
+ (dv, doc) => ((dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden)
+ );
}
@computed get titleButton() {
- return this.propertyToggleBtn("Title", "_showTitle", on => "Switch between title styles", on => "text-width", (dv, doc) => (dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? "title" : (dv?.rootDoc || doc)._showTitle === "title" ? "title:hover" : undefined);
+ return this.propertyToggleBtn(
+ 'Title',
+ '_showTitle',
+ on => 'Switch between title styles',
+ on => 'text-width',
+ (dv, doc) => ((dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? 'title' : (dv?.rootDoc || doc)._showTitle === 'title' ? 'title:hover' : undefined)
+ );
}
@computed get autoHeightButton() {
- return this.propertyToggleBtn("Auto\xA0Size", "_autoHeight", on => `Automatical vertical sizing to show all content`, on => "arrows-alt-v");
+ return this.propertyToggleBtn(
+ 'Auto\xA0Size',
+ '_autoHeight',
+ on => `Automatical vertical sizing to show all content`,
+ on => 'arrows-alt-v'
+ );
}
@computed get gridButton() {
- return this.propertyToggleBtn("Grid", "_backgroundGridShow", on => `Display background grid in collection`, on => "border-all");
+ return this.propertyToggleBtn(
+ 'Grid',
+ '_backgroundGridShow',
+ on => `Display background grid in collection`,
+ on => 'border-all'
+ );
}
@computed get groupButton() {
- return this.propertyToggleBtn("Group", "isGroup", on => `Display collection as a Group`, on => "object-group", (dv, doc) => { doc.isGroup = !doc.isGroup; doc.forceActive = doc.isGroup; });
+ return this.propertyToggleBtn(
+ 'Group',
+ 'isGroup',
+ on => `Display collection as a Group`,
+ on => 'object-group',
+ (dv, doc) => {
+ doc.isGroup = !doc.isGroup;
+ doc.forceActive = doc.isGroup;
+ }
+ );
}
@computed get freezeThumb() {
- return this.propertyToggleBtn("Freeze\Thumb", "_thumb-frozen", on => `${on ? "Freeze" : "Unfreeze"} thumbnail`, on => "arrows-alt-h", (dv, doc) => {
- if (doc["thumb-frozen"]) doc["thumb-frozen"] = undefined;
- else {
- document.body.focus(); // so that we can access the clipboard without an error
- setTimeout(() =>
- pasteImageBitmap((data: any, error: any) => {
- error && console.log(error);
- data && VideoBox.convertDataUri(data, doc[Id] + "-thumb-frozen", true).then(
- returnedfilename => doc["thumb-frozen"] = new ImageField(returnedfilename));
- }));
+ return this.propertyToggleBtn(
+ 'FreezeThumb',
+ '_thumb-frozen',
+ on => `${on ? 'Freeze' : 'Unfreeze'} thumbnail`,
+ on => 'arrows-alt-h',
+ (dv, doc) => {
+ if (doc['thumb-frozen']) doc['thumb-frozen'] = undefined;
+ else {
+ document.body.focus(); // so that we can access the clipboard without an error
+ setTimeout(() =>
+ pasteImageBitmap((data_url: any, error: any) => {
+ error && console.log(error);
+ data_url && VideoBox.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename)));
+ })
+ );
+ }
}
- });
+ );
}
@computed get snapButton() {
- return this.propertyToggleBtn("Snap\xA0Lines", "showSnapLines", on => `Display snapping lines when objects are dragged`, on => "border-all", undefined, true);
+ return this.propertyToggleBtn(
+ 'Snap\xA0Lines',
+ 'showSnapLines',
+ on => `Display snapping lines when objects are dragged`,
+ on => 'border-all',
+ undefined,
+ true
+ );
}
@computed
get onClickButton() {
- return !this.selectedDoc ? (null) : <Tooltip title={<div className="dash-tooltip">Choose onClick behavior</div>} placement="top">
- <div>
- <div className="propertiesButtons-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.onClickFlyout}>
- <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} >
- <FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />
- </div>
- </Flyout>
+ return !this.selectedDoc ? null : (
+ <Tooltip title={<div className="dash-tooltip">Choose onClick behavior</div>} placement="top">
+ <div>
+ <div className="propertiesButtons-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.onClickFlyout}>
+ <div className={'propertiesButtons-linkButton-empty'} onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />
+ </div>
+ </Flyout>
+ </div>
+ <div className="propertiesButtons-title"> onclick </div>
</div>
- <div className="propertiesButtons-title"> onclick </div>
- </div>
- </Tooltip>;
+ </Tooltip>
+ );
}
@computed
get perspectiveButton() {
- return !this.selectedDoc ? (null) : <Tooltip title={<div className="dash-tooltip">Choose view perspective</div>} placement="top">
- <div>
- <div className="propertiesButtons-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.onPerspectiveFlyout}>
- <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} >
- <FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />
- </div>
- </Flyout>
+ return !this.selectedDoc ? null : (
+ <Tooltip title={<div className="dash-tooltip">Choose view perspective</div>} placement="top">
+ <div>
+ <div className="propertiesButtons-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.onPerspectiveFlyout}>
+ <div className={'propertiesButtons-linkButton-empty'} onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />
+ </div>
+ </Flyout>
+ </div>
+ <div className="propertiesButtons-title"> Perspective </div>
</div>
- <div className="propertiesButtons-title"> Perspective </div>
- </div>
- </Tooltip>;
+ </Tooltip>
+ );
}
@undoBatch
handlePerspectiveChange = (e: any) => {
this.selectedDoc && (this.selectedDoc._viewType = e.target.value);
- SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => docView.layoutDoc._viewType = e.target.value);
- }
+ SelectionManager.Views()
+ .filter(dv => dv.docView)
+ .map(dv => dv.docView!)
+ .forEach(docView => (docView.layoutDoc._viewType = e.target.value));
+ };
@undoBatch
@action
handleOptionChange = (onClick: string) => {
this.selectedDoc && (this.selectedDoc.onClickBehavior = onClick);
- SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => {
- docView.noOnClick();
- switch (onClick) {
- case "enterPortal": docView.makeIntoPortal(); break;
- case "toggleDetail": docView.setToggleDetail(); break;
- case "linkInPlace": docView.toggleFollowLink("inPlace", true, false); break;
- case "linkOnRight": docView.toggleFollowLink("add:right", false, false); break;
- }
- });
- }
+ SelectionManager.Views()
+ .filter(dv => dv.docView)
+ .map(dv => dv.docView!)
+ .forEach(docView => {
+ docView.noOnClick();
+ switch (onClick) {
+ case 'enterPortal':
+ docView.makeIntoPortal();
+ break;
+ case 'toggleDetail':
+ docView.setToggleDetail();
+ break;
+ case 'linkInPlace':
+ docView.toggleFollowLink('inPlace', true, false);
+ break;
+ case 'linkOnRight':
+ docView.toggleFollowLink('add:right', false, false);
+ break;
+ }
+ });
+ };
@undoBatch
editOnClickScript = () => {
- if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, "onClick"));
- else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick");
- }
+ if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, 'onClick'));
+ else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
+ };
@computed
get onClickFlyout() {
const buttonList = [
- ["nothing", "Select Document"],
- ["enterPortal", "Enter Portal"],
- ["toggleDetail", "Toggle Detail"],
- ["linkInPlace", "Follow Link"],
- ["linkOnRight", "Open Link on Right"]
+ ['nothing', 'Select Document'],
+ ['enterPortal', 'Enter Portal'],
+ ['toggleDetail', 'Toggle Detail'],
+ ['linkInPlace', 'Follow Link'],
+ ['linkOnRight', 'Open Link on Right'],
];
const currentSelection = this.selectedDoc.onClickBehavior;
// Get items to place into the list
- const list = buttonList.map((value) => {
+ const list = buttonList.map(value => {
const click = () => {
this.handleOptionChange(value[0]);
};
- return <div className="list-item" key={`${value}`}
- style={{
- backgroundColor: value[0] === currentSelection ? Colors.LIGHT_BLUE : undefined
- }}
- onClick={click}>
- {value[1]}
- </div>;
+ return (
+ <div
+ className="list-item"
+ key={`${value}`}
+ style={{
+ backgroundColor: value[0] === currentSelection ? Colors.LIGHT_BLUE : undefined,
+ }}
+ onClick={click}>
+ {value[1]}
+ </div>
+ );
});
- return <div>
+ return (
<div>
- <div className="propertiesButton-dropdownList">
- {list}
+ <div>
+ <div className="propertiesButton-dropdownList">{list}</div>
</div>
+ {Doc.noviceMode ? null : (
+ <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript">
+ {' '}
+ Edit onClick Script
+ </div>
+ )}
</div>
- {Doc.UserDoc().noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>}
- </div>;
+ );
}
@computed
get onPerspectiveFlyout() {
const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear];
- const makeLabel = (value: string, label: string) => <div className="radio" key={label}>
- <label>
- <input type="radio" value={value} checked={(this.selectedDoc?._viewType ?? "invalid") === value} onChange={this.handlePerspectiveChange} />
- {label}
- </label>
- </div>;
- return <form>
- {Object.values(CollectionViewType).filter(type => !excludedViewTypes.includes(type)).map(type => makeLabel(type, type))}
- </form>;
+ const makeLabel = (value: string, label: string) => (
+ <div className="radio" key={label}>
+ <label>
+ <input type="radio" value={value} checked={(this.selectedDoc?._viewType ?? 'invalid') === value} onChange={this.handlePerspectiveChange} />
+ {label}
+ </label>
+ </div>
+ );
+ return (
+ <form>
+ {Object.values(CollectionViewType)
+ .filter(type => !excludedViewTypes.includes(type))
+ .map(type => makeLabel(type, type))}
+ </form>
+ );
}
render() {
@@ -231,27 +353,33 @@ export class PropertiesButtons extends React.Component<{}, {}> {
const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform;
const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree;
const isTabView = this.selectedTabView;
- const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => <div className="propertiesButtons-button" style={style}> {ele} </div>;
- const isNovice = Doc.UserDoc().noviceMode;
- return !this.selectedDoc ? (null) :
+ const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => (
+ <div className="propertiesButtons-button" style={style}>
+ {' '}
+ {ele}{' '}
+ </div>
+ );
+ const isNovice = Doc.noviceMode;
+ return !this.selectedDoc ? null : (
<div className="propertiesButtons">
{toggle(this.titleButton)}
{toggle(this.captionButton)}
{toggle(this.lockButton)}
- {toggle(this.dictationButton, { display: isNovice ? "none" : "" })}
+ {toggle(this.dictationButton, { display: isNovice ? 'none' : '' })}
{toggle(this.onClickButton)}
{toggle(this.fitWidthButton)}
{toggle(this.freezeThumb)}
- {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? "none" : "" })}
- {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? "none" : "" })}
- {toggle(this.maskButton, { display: !isInk ? "none" : "" })}
- {toggle(this.chromeButton, { display: !isCollection || isNovice ? "none" : "" })}
- {toggle(this.gridButton, { display: !isCollection ? "none" : "" })}
- {toggle(this.groupButton, { display: isTabView || !isCollection ? "none" : "" })}
- {toggle(this.snapButton, { display: !isCollection ? "none" : "" })}
- {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })}
- {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })}
- {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? "none" : "" })}
- </div>;
+ {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
+ {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
+ {toggle(this.maskButton, { display: !isInk ? 'none' : '' })}
+ {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })}
+ {toggle(this.gridButton, { display: !isCollection ? 'none' : '' })}
+ {toggle(this.groupButton, { display: isTabView || !isCollection ? 'none' : '' })}
+ {toggle(this.snapButton, { display: !isCollection ? 'none' : '' })}
+ {toggle(this.clustersButton, { display: !isFreeForm ? 'none' : '' })}
+ {toggle(this.panButton, { display: !isFreeForm ? 'none' : '' })}
+ {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? 'none' : '' })}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/PropertiesDocBacklinksSelector.scss b/src/client/views/PropertiesDocBacklinksSelector.scss
new file mode 100644
index 000000000..4d2a61c3b
--- /dev/null
+++ b/src/client/views/PropertiesDocBacklinksSelector.scss
@@ -0,0 +1,5 @@
+.propertiesView-contexts-content {
+ .linkMenu {
+ position: relative;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
new file mode 100644
index 000000000..4ead8eaf0
--- /dev/null
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -0,0 +1,53 @@
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { Doc, DocListCast } from "../../fields/Doc";
+import { Cast } from "../../fields/Types";
+import { emptyFunction } from "../../Utils";
+import { DocumentType } from "../documents/DocumentTypes";
+import { LinkManager } from "../util/LinkManager";
+import { SelectionManager } from "../util/SelectionManager";
+import { LinkMenu } from "./linking/LinkMenu";
+import './PropertiesDocBacklinksSelector.scss';
+
+type PropertiesDocBacklinksSelectorProps = {
+ Document: Doc,
+ Stack?: any,
+ hideTitle?: boolean,
+ addDocTab(doc: Doc, location: string): void
+};
+
+@observer
+export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDocBacklinksSelectorProps> {
+ @computed get _docs() {
+ const linkSource = this.props.Document;
+ const links = DocListCast(linkSource.links);
+ const collectedLinks = [] as Doc[];
+ links.map(link => {
+ const other = LinkManager.getOppositeAnchor(link, linkSource);
+ const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
+ if (otherdoc && !collectedLinks.some(d => Doc.AreProtosEqual(d, otherdoc))) {
+ collectedLinks.push(otherdoc);
+ }
+ });
+ return collectedLinks;
+ }
+
+ getOnClick = (link: Doc) => {
+ const linkSource = this.props.Document;
+ const other = LinkManager.getOppositeAnchor(link, linkSource);
+ const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
+
+ if (otherdoc) {
+ otherdoc.hidden = false;
+ this.props.addDocTab(Doc.IsPrototype(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, "toggle:right");
+ }
+ }
+
+ render() {
+ return !SelectionManager.Views().length ? (null) : <div>
+ {this.props.hideTitle ? (null) : <p key="contexts">Contexts:</p>}
+ <LinkMenu docView={SelectionManager.Views().lastElement()} clearLinkEditor={emptyFunction} itemHandler={this.getOnClick} position={{ x: 0 }} />
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 427748fe7..9d89ee036 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -1,58 +1,72 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { NumCast, StrCast } from "../../fields/Types";
-import { CollectionViewType } from "./collections/CollectionView";
-import { CollectionDockingView } from "./collections/CollectionDockingView";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { CollectionViewType } from '../documents/DocumentTypes';
+import { DocFocusOrOpen } from '../util/DocumentManager';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { DocumentView } from './nodes/DocumentView';
import './PropertiesDocContextSelector.scss';
-import { SearchUtil } from "../util/SearchUtil";
type PropertiesDocContextSelectorProps = {
- Document: Doc,
- Stack?: any,
- hideTitle?: boolean,
- addDocTab(doc: Doc, location: string): void
+ DocView?: DocumentView;
+ Stack?: any;
+ hideTitle?: boolean;
+ addDocTab(doc: Doc, location: string): void;
};
@observer
export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> {
- @observable private _docs: { col: Doc, target: Doc }[] = [];
- @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
- _reaction: IReactionDisposer | undefined;
-
- componentDidMount() { this._reaction = reaction(() => this.props.Document, () => this.fetchDocuments(), { fireImmediately: true }); }
- componentWillUnmount() { this._reaction?.(); }
- async fetchDocuments() {
- const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
- const containerProtoSets = await Promise.all(aliases.map(async alias => ((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
- const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
- const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => SearchUtil.GetAliasesOfDocument(container)));
- const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
- const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => SearchUtil.GetAliasesOfDocument(dp)));
- const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
- runInAction(() => {
- this._docs = doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).filter(doc => !Doc.IsSystem(doc)).map(doc => ({ col: doc, target: this.props.Document }));
- this._otherDocs = [];
- });
+ @computed get _docs() {
+ if (!this.props.DocView) return [];
+ const target = this.props.DocView.props.Document;
+ const targetContext = this.props.DocView.props.ContainingCollectionDoc;
+ const aliases = DocListCast(target.aliases);
+ const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc && Cast(alias.context, Doc, null) !== targetContext).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set<Doc>());
+ const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.aliases));
+ const containers = containerSets.reduce((p, set) => {
+ set.map(s => p.add(s));
+ return p;
+ }, new Set<Doc>());
+ const doclayoutSets = Array.from(containers.keys()).map(dp => DocListCast(dp.aliases));
+ const doclayouts = Array.from(
+ doclayoutSets
+ .reduce((p, set) => {
+ set.map(s => p.add(s));
+ return p;
+ }, new Set<Doc>())
+ .keys()
+ );
+ return doclayouts
+ .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
+ .filter(doc => !Doc.IsSystem(doc))
+ .map(doc => ({ col: doc, target }));
}
getOnClick = (col: Doc, target: Doc) => {
+ if (!this.props.DocView) return;
col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
if (col._viewType === CollectionViewType.Freeform) {
col._panX = NumCast(target.x) + NumCast(target._width) / 2;
col._panY = NumCast(target.y) + NumCast(target._height) / 2;
}
- this.props.addDocTab(col, "add:right");
- }
+ col.hidden = false;
+ this.props.addDocTab(col, 'toggle:right');
+ setTimeout(() => DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100);
+ };
render() {
- return <div>
- {this.props.hideTitle ? (null) : <p key="contexts">Contexts:</p>}
- {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a></p>)}
- {this._otherDocs.length ? <hr key="hr" /> : null}
- {this._otherDocs.map(doc => <p key={"p" + doc.col[Id] + doc.target[Id]}><a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a></p>)}
- </div>;
+ return (
+ <div>
+ {this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
+ {this._docs.map(doc => (
+ <p key={doc.col[Id] + doc.target[Id]}>
+ <a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a>
+ </p>
+ ))}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index d10ca61ed..ef0e057dc 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,57 +1,61 @@
-import React = require("react");
+import React = require('react');
+import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Checkbox, Tooltip } from "@material-ui/core";
-import { intersection } from "lodash";
-import { action, autorun, computed, Lambda, observable } from "mobx";
-import { observer } from "mobx-react";
-import { ColorState, SketchPicker } from "react-color";
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { InkField } from "../../fields/InkField";
-import { List } from "../../fields/List";
-import { ComputedField } from "../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
-import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils";
-import { DocumentType } from "../documents/DocumentTypes";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { DocumentManager } from "../util/DocumentManager";
-import { LinkManager } from "../util/LinkManager";
-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 { CollectionViewType } from "./collections/CollectionView";
-import { EditableView } from "./EditableView";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { DocumentView, StyleProviderFunc } from "./nodes/DocumentView";
-import { KeyValueBox } from "./nodes/KeyValueBox";
-import { PropertiesButtons } from "./PropertiesButtons";
-import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector";
-import "./PropertiesView.scss";
-import { DefaultStyleProvider } from "./StyleProvider";
-import { PresBox } from "./nodes/trails";
-import { IconLookup } from "@fortawesome/fontawesome-svg-core";
-const higflyout = require("@hig/flyout");
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Checkbox, Tooltip } from '@material-ui/core';
+import { intersection } from 'lodash';
+import { action, autorun, computed, Lambda, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { ColorState, SketchPicker } from 'react-color';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { InkField } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { ComputedField } from '../../fields/ScriptField';
+import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
+import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from '../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocumentManager } from '../util/DocumentManager';
+import { LinkManager } from '../util/LinkManager';
+import { SelectionManager } from '../util/SelectionManager';
+import { SharingManager } from '../util/SharingManager';
+import { Transform } from '../util/Transform';
+import { undoBatch, UndoManager } from '../util/UndoManager';
+import { EditableView } from './EditableView';
+import { InkStrokeProperties } from './InkStrokeProperties';
+import { DocumentView, StyleProviderFunc } from './nodes/DocumentView';
+import { FilterBox } from './nodes/FilterBox';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { PresBox } from './nodes/trails';
+import { PropertiesButtons } from './PropertiesButtons';
+import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector';
+import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
+import './PropertiesView.scss';
+import { DefaultStyleProvider } from './StyleProvider';
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-const _global = (window /* browser */ || global /* node */) as any;
+const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
width: number;
height: number;
styleProvider?: StyleProviderFunc;
+ addDocTab: (doc: Doc, where: string) => boolean;
}
@observer
export class PropertiesView extends React.Component<PropertiesViewProps> {
private _widthUndo?: UndoManager.Batch;
- @computed get MAX_EMBED_HEIGHT() { return 200; }
+ @computed get MAX_EMBED_HEIGHT() {
+ return 200;
+ }
- @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || CurrentUserUtils.ActiveDashboard; }
+ @computed get selectedDoc() {
+ return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard;
+ }
@computed get selectedDocumentView() {
if (SelectionManager.Views().length) return SelectionManager.Views()[0];
if (PresBox.Instance?._selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
@@ -63,7 +67,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get isLink(): boolean {
return this.selectedDoc?.type === DocumentType.LINK;
}
- @computed get dataDoc() { return this.selectedDoc?.[DataSym]; }
+ @computed get dataDoc() {
+ return this.selectedDoc?.[DataSym];
+ }
@observable layoutFields: boolean = false;
@@ -72,6 +78,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openFields: boolean = true;
@observable openLayout: boolean = false;
@observable openContexts: boolean = true;
+ @observable openLinks: boolean = true;
@observable openAppearance: boolean = true;
@observable openTransform: boolean = true;
@observable openFilters: boolean = true; // should be false
@@ -104,14 +111,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
this.selectedDocListenerDisposer?.();
}
- @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; }
+ @computed get isInk() {
+ return this.selectedDoc?.type === DocumentType.INK;
+ }
rtfWidth = () => {
return !this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20);
- }
+ };
rtfHeight = () => {
return !this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
- }
+ };
@action
docWidth = () => {
@@ -123,62 +132,74 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
} else {
return 0;
}
- }
+ };
@action
docHeight = () => {
if (this.selectedDoc && this.dataDoc) {
const layoutDoc = this.selectedDoc;
- return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT,
- Doc.NativeAspect(layoutDoc, undefined, true) ? this.docWidth() / Doc.NativeAspect(layoutDoc, undefined, true) :
- layoutDoc._fitWidth ? (!Doc.NativeHeight(this.dataDoc) ? NumCast(this.props.height) :
- Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))) :
- NumCast(layoutDoc._height) || 50));
+ return Math.max(
+ 70,
+ Math.min(
+ this.MAX_EMBED_HEIGHT,
+ Doc.NativeAspect(layoutDoc, undefined, true)
+ ? this.docWidth() / Doc.NativeAspect(layoutDoc, undefined, true)
+ : layoutDoc._fitWidth
+ ? !Doc.NativeHeight(this.dataDoc)
+ ? NumCast(this.props.height)
+ : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))
+ : NumCast(layoutDoc._height) || 50
+ )
+ );
}
return 0;
- }
+ };
@computed get expandedField() {
if (this.dataDoc && this.selectedDoc) {
const ids: { [key: string]: string } = {};
- const docs = SelectionManager.Views().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] :
- SelectionManager.Views().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc);
+ const docs = SelectionManager.Views().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
const rows: JSX.Element[] = [];
for (const key of Object.keys(ids).slice().sort()) {
const docvals = new Set<any>();
docs.forEach(doc => docvals.add(doc[key]));
- const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
- if (key[0] === "#") {
- rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "2px" }} key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
- &nbsp;
- </div>);
+ const contents = Array.from(docvals.keys()).length > 1 ? '-multiple' : docs[0][key];
+ if (key[0] === '#') {
+ rows.push(
+ <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '2px' }} key={key}>
+ <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key}</span>
+ &nbsp;
+ </div>
+ );
} else {
- const contentElement = <EditableView key="editableView"
- contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
- height={13}
- fontSize={10}
- GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
- SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
- />;
- rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
- &nbsp;
- {contentElement}
- </div>);
+ const contentElement = (
+ <EditableView
+ key="editableView"
+ contents={contents !== undefined ? Field.toString(contents as Field) : 'null'}
+ height={13}
+ fontSize={10}
+ GetValue={() => (contents !== undefined ? Field.toString(contents as Field) : 'null')}
+ SetValue={(value: string) => {
+ docs.map(doc => KeyValueBox.SetField(doc, key, value, true));
+ return true;
+ }}
+ />
+ );
+ rows.push(
+ <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
+ <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
+ &nbsp;
+ {contentElement}
+ </div>
+ );
}
}
- rows.push(<div className="propertiesView-field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
- <EditableView
- key="editableView"
- oneLine
- contents={"add key:value or #tags"}
- height={13}
- fontSize={10}
- GetValue={() => ""}
- SetValue={this.setKeyValue} />
- </div>);
+ rows.push(
+ <div className="propertiesView-field" key={'newKeyValue'} style={{ marginTop: '3px' }}>
+ <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} />
+ </div>
+ );
return rows;
}
}
@@ -189,74 +210,80 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const docs = SelectionManager.Views().length < 2 ? [this.dataDoc] : SelectionManager.Views().map(dv => dv.dataDoc);
docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)));
const rows: JSX.Element[] = [];
- const noviceReqFields = ["author", "creationDate", "tags"];
- const noviceLayoutFields = ["_curPage"];
- const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("acl"))),
- ...noviceReqFields, ...noviceLayoutFields];
+ const noviceReqFields = ['author', 'creationDate', 'tags'];
+ const noviceLayoutFields = ['_curPage'];
+ const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl'))), ...noviceReqFields, ...noviceLayoutFields];
for (const key of noviceKeys.sort()) {
const docvals = new Set<any>();
docs.forEach(doc => docvals.add(doc[key]));
- const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key];
- if (key[0] === "#") {
- rows.push(<div className="propertiesView-uneditable-field" key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
- &nbsp;
- </div>);
+ const contents = Array.from(docvals.keys()).length > 1 ? '-multiple' : docs[0][key];
+ if (key[0] === '#') {
+ rows.push(
+ <div className="propertiesView-uneditable-field" key={key}>
+ <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key}</span>
+ &nbsp;
+ </div>
+ );
} else if (contents !== undefined) {
const value = Field.toString(contents as Field);
- if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) {
- rows.push(<div className="propertiesView-uneditable-field" key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ": "}</span>
- <div style={{ whiteSpace: "nowrap", overflowX: "hidden" }}>{value}</div>
- </div>);
+ if (noviceReqFields.includes(key) || key.indexOf('lastModified') !== -1) {
+ rows.push(
+ <div className="propertiesView-uneditable-field" key={key}>
+ <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ': '}</span>
+ <div style={{ whiteSpace: 'nowrap', overflowX: 'hidden' }}>{value}</div>
+ </div>
+ );
} else {
- const contentElement = <EditableView key="editableView"
- contents={value}
- height={13}
- fontSize={10}
- GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"}
- SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }}
- />;
+ const contentElement = (
+ <EditableView
+ key="editableView"
+ contents={value}
+ height={13}
+ fontSize={10}
+ GetValue={() => (contents !== undefined ? Field.toString(contents as Field) : 'null')}
+ SetValue={(value: string) => {
+ docs.map(doc => KeyValueBox.SetField(doc, key, value, true));
+ return true;
+ }}
+ />
+ );
- rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
- <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
- &nbsp;
- {contentElement}
- </div>);
+ rows.push(
+ <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
+ <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
+ &nbsp;
+ {contentElement}
+ </div>
+ );
}
}
}
- rows.push(<div className="propertiesView-field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
- <EditableView
- key="editableView"
- oneLine
- contents={"add key:value or #tags"}
- height={13}
- fontSize={10}
- GetValue={() => ""}
- SetValue={this.setKeyValue} />
- </div>);
+ rows.push(
+ <div className="propertiesView-field" key={'newKeyValue'} style={{ marginTop: '3px' }}>
+ <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} />
+ </div>
+ );
return rows;
}
}
@undoBatch
setKeyValue = (value: string) => {
- const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc);
+ const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
docs.forEach(doc => {
- if (value.indexOf(":") !== -1) {
+ if (value.indexOf(':') !== -1) {
const newVal = value[0].toUpperCase() + value.substring(1, value.length);
- const splits = newVal.split(":");
+ const splits = newVal.split(':');
KeyValueBox.SetField(doc, splits[0], splits[1], true);
- const tags = StrCast(doc.tags, ":");
- if (tags.includes(`${splits[0]}:`) && splits[1] === "undefined") {
- KeyValueBox.SetField(doc, "tags", `"${tags.replace(splits[0] + ":", "")}"`, true);
+ const tags = StrCast(doc.tags, ':');
+ if (tags.includes(`${splits[0]}:`) && splits[1] === 'undefined') {
+ KeyValueBox.SetField(doc, 'tags', `"${tags.replace(splits[0] + ':', '')}"`, true);
}
return true;
- } else if (value[0] === "#") {
+ } else if (value[0] === '#') {
const newVal = value + `:'${value}'`;
doc[DataSym][value] = value;
- const tags = StrCast(doc.tags, ":");
+ const tags = StrCast(doc.tags, ':');
if (!tags.includes(`${value}:`)) {
doc[DataSym].tags = `${tags + value + ':'}`;
}
@@ -264,66 +291,72 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
});
return false;
- }
+ };
@observable transform: Transform = Transform.Identity();
getTransform = () => this.transform;
propertiesDocViewRef = (ref: HTMLDivElement) => {
- const observer = new _global.ResizeObserver(action((entries: any) => {
- const cliRect = ref.getBoundingClientRect();
- this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
- }));
+ const observer = new _global.ResizeObserver(
+ action((entries: any) => {
+ const cliRect = ref.getBoundingClientRect();
+ this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
+ })
+ );
ref && observer.observe(ref);
- }
+ };
@computed get contexts() {
- return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
+ return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this.props.addDocTab} />;
+ }
+
+ @computed get links() {
+ return !this.selectedDoc ? null : <PropertiesDocBacklinksSelector Document={this.selectedDoc} hideTitle={true} addDocTab={this.props.addDocTab} />;
}
@computed get layoutPreview() {
if (SelectionManager.Views().length > 1) {
- return "-- multiple selected --";
+ return '-- multiple selected --';
}
if (this.selectedDoc) {
const layoutDoc = Doc.Layout(this.selectedDoc);
- const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight;
- const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth;
- return <div ref={this.propertiesDocViewRef} style={{ pointerEvents: "none", display: "inline-block", height: panelHeight() }} key={this.selectedDoc[Id]}>
- <DocumentView
- Document={layoutDoc}
- DataDoc={this.dataDoc}
- renderDepth={1}
- fitContentsToDoc={returnTrue}
- rootSelected={returnFalse}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- docViewPath={returnEmptyDoclist}
- freezeDimensions={true}
- dontCenter={"y"}
- isDocumentActive={returnFalse}
- isContentActive={emptyFunction}
- NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
- NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
- PanelWidth={panelWidth}
- PanelHeight={panelHeight}
- focus={returnFalse}
- ScreenToLocalTransform={this.getTransform}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- addDocument={returnFalse}
- moveDocument={undefined}
- removeDocument={returnFalse}
- whenChildContentsActiveChanged={emptyFunction}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- bringToFront={returnFalse}
- dontRegisterView={true}
- dropAction={undefined}
- />
- </div>;
+ const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfHeight : this.docHeight;
+ const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfWidth : this.docWidth;
+ return (
+ <div ref={this.propertiesDocViewRef} style={{ pointerEvents: 'none', display: 'inline-block', height: panelHeight() }} key={this.selectedDoc[Id]}>
+ <DocumentView
+ Document={layoutDoc}
+ DataDoc={this.dataDoc}
+ renderDepth={1}
+ fitContentsToBox={returnTrue}
+ rootSelected={returnFalse}
+ styleProvider={DefaultStyleProvider}
+ docViewPath={returnEmptyDoclist}
+ dontCenter={'y'}
+ isDocumentActive={returnFalse}
+ isContentActive={emptyFunction}
+ NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
+ NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
+ PanelWidth={panelWidth}
+ PanelHeight={panelHeight}
+ focus={returnFalse}
+ ScreenToLocalTransform={this.getTransform}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ addDocument={returnFalse}
+ moveDocument={undefined}
+ removeDocument={returnFalse}
+ whenChildContentsActiveChanged={emptyFunction}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ bringToFront={returnFalse}
+ dontRegisterView={true}
+ dropAction={undefined}
+ />
+ </div>
+ );
} else {
return null;
}
@@ -334,67 +367,85 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
*/
@undoBatch
changePermissions = (e: any, user: string) => {
- const docs = SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.props.Document);
+ const docs = SelectionManager.Views().length < 2 ? (this.selectedDoc ? [this.selectedDoc] : []) : SelectionManager.Views().map(docView => docView.props.Document);
SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs);
- }
+ };
/**
* @returns the options for the permissions dropdown.
*/
getPermissionsSelect(user: string, permission: string) {
const dropdownValues: string[] = Object.values(SharingPermissions);
- if (permission === "-multiple-") dropdownValues.unshift(permission);
- if (user === "Override") dropdownValues.unshift("None");
- return <select className="permissions-select"
- value={permission}
- onChange={e => this.changePermissions(e, user)}>
- {dropdownValues.filter(permission =>
- !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
- <option key={permission} value={permission}> {permission} </option>)}
- </select>;
+ if (permission === '-multiple-') dropdownValues.unshift(permission);
+ if (user === 'Override') dropdownValues.unshift('None');
+ return (
+ <select className="permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}>
+ {dropdownValues
+ .filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any))
+ .map(permission => (
+ <option key={permission} value={permission}>
+ {' '}
+ {permission}{' '}
+ </option>
+ ))}
+ </select>
+ );
}
/**
* @returns the notification icon. On clicking, it should notify someone of a document been shared with them.
*/
@computed get notifyIcon() {
- return <Tooltip title={<div className="dash-tooltip">Notify with message</div>}>
- <div className="notify-button">
- <FontAwesomeIcon className="notify-button-icon" icon="bell" color="white" size="sm" />
- </div>
- </Tooltip>;
+ return (
+ <Tooltip title={<div className="dash-tooltip">Notify with message</div>}>
+ <div className="notify-button">
+ <FontAwesomeIcon className="notify-button-icon" icon="bell" color="white" size="sm" />
+ </div>
+ </Tooltip>
+ );
}
/**
* ... next to the owner that opens the main SharingManager interface on click.
*/
@computed get expansionIcon() {
- return <Tooltip title={<div className="dash-tooltip">{"Show more permissions"}</div>}>
- <div className="expansion-button" onPointerDown={() => {
- if (this.selectedDocumentView || this.selectedDoc) {
- SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
- }
- }}>
- <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
- </div>
- </Tooltip>;
+ return (
+ <Tooltip title={<div className="dash-tooltip">{'Show more permissions'}</div>}>
+ <div
+ className="expansion-button"
+ onPointerDown={() => {
+ if (this.selectedDocumentView || this.selectedDoc) {
+ SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
+ }
+ }}>
+ <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
+ </div>
+ </Tooltip>
+ );
}
/**
* @returns a row of the permissions panel
*/
sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
- return <div className="propertiesView-sharingTable-item" key={name + permission}
- // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }}
- // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)}
- >
- <div className="propertiesView-sharingTable-item-name" style={{ width: name !== "Me" ? "85px" : "80px" }}> {name} </div>
- {/* {name !== "Me" ? this.notifyIcon : null} */}
- <div className="propertiesView-sharingTable-item-permission">
- {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission}
- {permission === "Owner" || showExpansionIcon ? this.expansionIcon : null}
+ return (
+ <div
+ className="propertiesView-sharingTable-item"
+ key={name + permission}
+ // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }}
+ // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)}
+ >
+ <div className="propertiesView-sharingTable-item-name" style={{ width: name !== 'Me' ? '85px' : '80px' }}>
+ {' '}
+ {name}{' '}
+ </div>
+ {/* {name !== "Me" ? this.notifyIcon : null} */}
+ <div className="propertiesView-sharingTable-item-permission">
+ {admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : permission}
+ {permission === 'Owner' || showExpansionIcon ? this.expansionIcon : null}
+ </div>
</div>
- </div>;
+ );
}
/**
@@ -402,19 +453,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
*/
@computed get sharingTable() {
const AclMap = new Map<symbol, string>([
- [AclUnset, "None"],
+ [AclUnset, 'None'],
[AclPrivate, SharingPermissions.None],
[AclReadonly, SharingPermissions.View],
[AclAugment, SharingPermissions.Augment],
[AclSelfEdit, SharingPermissions.SelfEdit],
[AclEdit, SharingPermissions.Edit],
- [AclAdmin, SharingPermissions.Admin]
+ [AclAdmin, SharingPermissions.Admin],
]);
// all selected docs
- const docs = SelectionManager.Views().length < 2 ?
- [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc[DataSym]]
- : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]);
+ const docs =
+ SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]));
const target = docs[0];
@@ -423,7 +473,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const showAdmin = effectiveAcls.every(acl => acl === AclAdmin);
// users in common between all docs
- const commonKeys: string[] = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym])));
+ const commonKeys: string[] = intersection(...docs.map(doc => (this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym]))));
const tableEntries = [];
@@ -431,9 +481,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
if (commonKeys.length) {
for (const key of commonKeys) {
const name = denormalizeEmail(key.substring(4));
- const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]);
- if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) {
- tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-"));
+ const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]));
+ if (name !== Doc.CurrentUserEmail && name !== target.author && name !== 'Public' && name !== 'Override' /* && sidebarUsersDisplayed![name] !== false*/) {
+ tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : '-multiple-'));
}
}
}
@@ -441,62 +491,53 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author);
// shifts the current user, owner, public to the top of the doc.
// tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-"));
- tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Public"] === docs[0]["acl-Public"]) ? (AclMap.get(target[AclSym]?.["acl-Public"]) || SharingPermissions.None) : "-multiple-"));
- tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame));
- if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner"));
+ tableEntries.unshift(this.sharingItem('Public', showAdmin, docs.filter(doc => doc).every(doc => doc['acl-Public'] === docs[0]['acl-Public']) ? AclMap.get(target[AclSym]?.['acl-Public']) || SharingPermissions.None : '-multiple-'));
+ tableEntries.unshift(
+ this.sharingItem('Me', showAdmin, docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? 'Owner' : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : '-multiple-', !ownerSame)
+ );
+ if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'));
- return <div className="propertiesView-sharingTable">
- {tableEntries}
- </div>;
+ return <div className="propertiesView-sharingTable">{tableEntries}</div>;
}
@computed get fieldsCheckbox() {
- return <Checkbox
- color="primary"
- onChange={this.toggleCheckbox}
- checked={this.layoutFields}
- />;
+ return <Checkbox color="primary" onChange={this.toggleCheckbox} checked={this.layoutFields} />;
}
@action
toggleCheckbox = () => {
this.layoutFields = !this.layoutFields;
- }
+ };
@computed get editableTitle() {
const titles = new Set<string>();
SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
- const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title);
- return <div className="editable-title">
- <EditableView
- key="editableView"
- contents={title}
- height={25}
- fontSize={14}
- GetValue={() => title}
- SetValue={this.setTitle} />
- </div>;
+ const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
+ return (
+ <div className="editable-title">
+ <EditableView key="editableView" contents={title} height={25} fontSize={14} GetValue={() => title} SetValue={this.setTitle} />
+ </div>
+ );
}
@undoBatch
@action
setTitle = (value: string) => {
if (SelectionManager.Views().length > 1) {
- SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true));
+ SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, 'title', value, true));
return true;
} else if (this.dataDoc) {
- if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true);
- else KeyValueBox.SetField(this.dataDoc, "title", value, true);
+ if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true);
+ else KeyValueBox.SetField(this.dataDoc, 'title', value, true);
return true;
}
return false;
- }
-
+ };
@undoBatch
@action
rotate = (angle: number) => {
- const _centerPoints: { X: number, Y: number }[] = [];
+ const _centerPoints: { X: number; Y: number }[] = [];
if (this.selectedDoc) {
const doc = this.selectedDoc;
if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
@@ -517,7 +558,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
doc.rotation = NumCast(doc.rotation) + angle;
const inks = Cast(doc.data, InkField)?.inkData;
if (inks) {
- const newPoints: { X: number, Y: number }[] = [];
+ const newPoints: { X: number; Y: number }[] = [];
inks.forEach(ink => {
const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X;
const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
@@ -531,117 +572,134 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- doc._height = (bottom - top);
- doc._width = (right - left);
+ doc._height = bottom - top;
+ doc._width = right - left;
}
index++;
}
}
- }
+ };
@computed
get controlPointsButton() {
- return <div className="inking-button">
- <Tooltip title={<div className="dash-tooltip">{"Edit points"}</div>}>
- <div className="inking-button-points"
- style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? "black" : "" }}
- onPointerDown={action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)} >
- <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
- </div>
- </Tooltip>
- <Tooltip title={<div className="dash-tooltip">{InkStrokeProperties.Instance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
- <div className="inking-button-lock" onPointerDown={action(() => InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock)} >
- <FontAwesomeIcon icon={InkStrokeProperties.Instance._lock ? "lock" : "unlock"} color="white" size="lg" />
- </div>
- </Tooltip>
- <Tooltip title={<div className="dash-tooltip">{"Rotate 90˚"}</div>}>
- <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
- <FontAwesomeIcon icon="undo" color="white" size="lg" />
- </div>
- </Tooltip>
- </div>;
+ return (
+ <div className="inking-button">
+ <Tooltip title={<div className="dash-tooltip">{'Edit points'}</div>}>
+ <div
+ className="inking-button-points"
+ style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? 'black' : '' }}
+ onPointerDown={action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}>
+ <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">{InkStrokeProperties.Instance._lock ? 'Unlock ratio' : 'Lock ratio'}</div>}>
+ <div className="inking-button-lock" onPointerDown={action(() => (InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock))}>
+ <FontAwesomeIcon icon={InkStrokeProperties.Instance._lock ? 'lock' : 'unlock'} color="white" size="lg" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">{'Rotate 90˚'}</div>}>
+ <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
+ <FontAwesomeIcon icon="undo" color="white" size="lg" />
+ </div>
+ </Tooltip>
+ </div>
+ );
}
inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
- return <div className="inputBox"
- style={{
- marginRight: title === "X:" ? "19px" : "",
- marginLeft: title === "∠:" ? "39px" : ""
- }}>
- <div className="inputBox-title"> {title} </div>
- <input className="inputBox-input"
- type="text" value={value}
- onChange={e => {
- setter(e.target.value);
- }}
- onKeyPress={e => {
- e.stopPropagation();
- }} />
- <div className="inputBox-button">
- <div className="inputBox-button-up" key="up2"
- onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
- <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
- </div>
- <div className="inputbox-Button-down" key="down2"
- onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
- <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ return (
+ <div
+ className="inputBox"
+ style={{
+ marginRight: title === 'X:' ? '19px' : '',
+ marginLeft: title === '∠:' ? '39px' : '',
+ }}>
+ <div className="inputBox-title"> {title} </div>
+ <input
+ className="inputBox-input"
+ type="text"
+ value={value}
+ onChange={e => {
+ setter(e.target.value);
+ }}
+ onKeyPress={e => {
+ e.stopPropagation();
+ }}
+ />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
+ <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+ <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ </div>
</div>
</div>
- </div>;
- }
+ );
+ };
inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
- return <div className="inputBox-duo">
- {this.inputBox(key, value, setter, title1)}
- {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)}
- </div>;
- }
+ return (
+ <div className="inputBox-duo">
+ {this.inputBox(key, value, setter, title1)}
+ {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)}
+ </div>
+ );
+ };
@action
upDownButtons = (dirs: string, field: string) => {
switch (field) {
- case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
- case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break;
- case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break;
- case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
- case "wid":
+ case 'rot':
+ this.rotate(dirs === 'up' ? 0.1 : -0.1);
+ break;
+ case 'Xps':
+ this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10));
+ break;
+ case 'Yps':
+ this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10));
+ break;
+ case 'stk':
+ this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === 'up' ? 0.1 : -0.1));
+ break;
+ case 'wid':
const oldWidth = NumCast(this.selectedDoc?._width);
const oldHeight = NumCast(this.selectedDoc?._height);
const oldX = NumCast(this.selectedDoc?.x);
const oldY = NumCast(this.selectedDoc?.y);
- this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10));
- InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
+ this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === 'up' ? 10 : -10));
+ InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth) * NumCast(this.selectedDoc?._height));
const doc = this.selectedDoc;
if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
const ink = Cast(doc.data, InkField)?.inkData;
if (ink) {
- const newPoints: { X: number, Y: number }[] = [];
+ const newPoints: { X: number; Y: number }[] = [];
for (var j = 0; j < ink.length; j++) {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth;
- const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight;
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = NumCast(doc.x) - oldX + (ink[j].X * NumCast(doc._width)) / oldWidth;
+ const newY = NumCast(doc.y) - oldY + (ink[j].Y * NumCast(doc._height)) / oldHeight;
newPoints.push({ X: newX, Y: newY });
}
doc.data = new InkField(newPoints);
}
}
break;
- case "hgt":
+ case 'hgt':
const oWidth = NumCast(this.selectedDoc?._width);
const oHeight = NumCast(this.selectedDoc?._height);
const oX = NumCast(this.selectedDoc?.x);
const oY = NumCast(this.selectedDoc?.y);
- this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10));
- InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
+ this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === 'up' ? 10 : -10));
+ InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight) * NumCast(this.selectedDoc?._width));
const docu = this.selectedDoc;
if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
const ink = Cast(docu.data, InkField)?.inkData;
if (ink) {
- const newPoints: { X: number, Y: number }[] = [];
+ const newPoints: { X: number; Y: number }[] = [];
for (var j = 0; j < ink.length; j++) {
- // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth;
- const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight;
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = NumCast(docu.x) - oX + (ink[j].X * NumCast(docu._width)) / oWidth;
+ const newY = NumCast(docu.y) - oY + (ink[j].Y * NumCast(docu._height)) / oHeight;
newPoints.push({ X: newX, Y: newY });
}
docu.data = new InkField(newPoints);
@@ -649,7 +707,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
break;
}
- }
+ };
getField(key: string) {
//if (this.selectedDoc) {
@@ -659,14 +717,30 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
// }
}
- @computed get shapeXps() { return this.getField("x"); }
- @computed get shapeYps() { return this.getField("y"); }
- @computed get shapeRot() { return this.getField("rotation"); }
- @computed get shapeHgt() { return this.getField("_height"); }
- @computed get shapeWid() { return this.getField("_width"); }
- set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); }
- set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); }
- set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); }
+ @computed get shapeXps() {
+ return this.getField('x');
+ }
+ @computed get shapeYps() {
+ return this.getField('y');
+ }
+ @computed get shapeRot() {
+ return this.getField('rotation');
+ }
+ @computed get shapeHgt() {
+ return this.getField('_height');
+ }
+ @computed get shapeWid() {
+ return this.getField('_width');
+ }
+ set shapeXps(value) {
+ this.selectedDoc && (this.selectedDoc.x = Number(value));
+ }
+ set shapeYps(value) {
+ this.selectedDoc && (this.selectedDoc.y = Number(value));
+ }
+ set shapeRot(value) {
+ this.selectedDoc && (this.selectedDoc.rotation = Number(value));
+ }
set shapeWid(value) {
const oldWidth = NumCast(this.selectedDoc?._width);
this.selectedDoc && (this.selectedDoc._width = Number(value));
@@ -678,38 +752,122 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
}
- @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); }
- @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); }
- @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); }
-
+ @computed get hgtInput() {
+ return this.inputBoxDuo(
+ 'hgt',
+ this.shapeHgt,
+ (val: string) => {
+ if (!isNaN(Number(val))) {
+ this.shapeHgt = val;
+ }
+ return true;
+ },
+ 'H:',
+ 'wid',
+ this.shapeWid,
+ (val: string) => {
+ if (!isNaN(Number(val))) {
+ this.shapeWid = val;
+ }
+ return true;
+ },
+ 'W:'
+ );
+ }
+ @computed get XpsInput() {
+ return this.inputBoxDuo(
+ 'Xps',
+ this.shapeXps,
+ (val: string) => {
+ if (val !== '0' && !isNaN(Number(val))) {
+ this.shapeXps = val;
+ }
+ return true;
+ },
+ 'X:',
+ 'Yps',
+ this.shapeYps,
+ (val: string) => {
+ if (val !== '0' && !isNaN(Number(val))) {
+ this.shapeYps = val;
+ }
+ return true;
+ },
+ 'Y:'
+ );
+ }
+ @computed get rotInput() {
+ return this.inputBoxDuo(
+ 'rot',
+ this.shapeRot,
+ (val: string) => {
+ if (!isNaN(Number(val))) {
+ this.rotate(Number(val) - Number(this.shapeRot));
+ this.shapeRot = val;
+ }
+ return true;
+ },
+ '∠:',
+ 'rot',
+ this.shapeRot,
+ (val: string) => {
+ if (!isNaN(Number(val))) {
+ this.rotate(Number(val) - Number(this.shapeRot));
+ this.shapeRot = val;
+ }
+ return true;
+ },
+ ''
+ );
+ }
@observable private _fillBtn = false;
@observable private _lineBtn = false;
- private _lastFill = "#D0021B";
- private _lastLine = "#D0021B";
- private _lastDash: any = "2";
+ private _lastFill = '#D0021B';
+ private _lastLine = '#D0021B';
+ private _lastDash: any = '2';
- @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
- @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
- set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); }
- set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); }
+ @computed get colorFil() {
+ const ccol = this.getField('fillColor') || '';
+ ccol && (this._lastFill = ccol);
+ return ccol;
+ }
+ @computed get colorStk() {
+ const ccol = this.getField('color') || '';
+ ccol && (this._lastLine = ccol);
+ return ccol;
+ }
+ set colorFil(value) {
+ value && (this._lastFill = value);
+ this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined);
+ }
+ set colorStk(value) {
+ value && (this._lastLine = value);
+ this.selectedDoc && (this.selectedDoc.color = value ? value : undefined);
+ }
colorButton(value: string, type: string, setter: () => {}) {
// return <div className="properties-flyout" onPointerEnter={e => this.changeScrolling(false)}
// onPointerLeave={e => this.changeScrolling(true)}>
// <Flyout anchorPoint={anchorPoints.LEFT_TOP}
// content={type === "fill" ? this.fillPicker : this.linePicker}>
- return <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}>
- <div className="color-button-preview" style={{
- backgroundColor: value ?? "121212", width: 15, height: 15,
- display: value === "" || value === "transparent" ? "none" : ""
- }} />
- {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14 }}>☒</p> : ""}
- </div>;
+ return (
+ <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}>
+ <div
+ className="color-button-preview"
+ style={{
+ backgroundColor: value ?? '121212',
+ width: 15,
+ height: 15,
+ display: value === '' || value === 'transparent' ? 'none' : '',
+ }}
+ />
+ {value === '' || value === 'transparent' ? <p style={{ fontSize: 25, color: 'red', marginTop: -14 }}>☒</p> : ''}
+ </div>
+ );
// </Flyout>
// </div>;
-
}
@undoBatch
@@ -718,206 +876,271 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const val = String(color.hex);
this.colorStk = val;
return true;
- }
+ };
@undoBatch
@action
switchFil = (color: ColorState) => {
const val = String(color.hex);
this.colorFil = val;
return true;
- }
+ };
colorPicker(setter: (color: string) => {}, type: string) {
- return <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
- '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
- '#FFFFFF', '#f1efeb', 'transparent']}
- color={type === "stk" ? this.colorStk : this.colorFil} />;
+ return (
+ <SketchPicker
+ onChange={type === 'stk' ? this.switchStk : this.switchFil}
+ presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ color={type === 'stk' ? this.colorStk : this.colorFil}
+ />
+ );
}
- @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
- @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); }
+ @computed get fillButton() {
+ return this.colorButton(this.colorFil, 'fill', () => {
+ this._fillBtn = !this._fillBtn;
+ this._lineBtn = false;
+ return true;
+ });
+ }
+ @computed get lineButton() {
+ return this.colorButton(this.colorStk, 'line', () => {
+ this._lineBtn = !this._lineBtn;
+ this._fillBtn = false;
+ return true;
+ });
+ }
- @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); }
- @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); }
+ @computed get fillPicker() {
+ return this.colorPicker((color: string) => (this.colorFil = color), 'fil');
+ }
+ @computed get linePicker() {
+ return this.colorPicker((color: string) => (this.colorStk = color), 'stk');
+ }
@computed get strokeAndFill() {
- return <div>
- <div key="fill" className="strokeAndFill">
- <div className="fill">
- <div className="fill-title">Fill:</div>
- <div className="fill-button">{this.fillButton}</div>
- </div>
- <div className="stroke">
- <div className="stroke-title"> Stroke: </div>
- <div className="stroke-button">{this.lineButton}</div>
+ return (
+ <div>
+ <div key="fill" className="strokeAndFill">
+ <div className="fill">
+ <div className="fill-title">Fill:</div>
+ <div className="fill-button">{this.fillButton}</div>
+ </div>
+ <div className="stroke">
+ <div className="stroke-title"> Stroke: </div>
+ <div className="stroke-button">{this.lineButton}</div>
+ </div>
</div>
+ {this._fillBtn ? this.fillPicker : ''}
+ {this._lineBtn ? this.linePicker : ''}
</div>
- {this._fillBtn ? this.fillPicker : ""}
- {this._lineBtn ? this.linePicker : ""}
- </div>;
- }
-
- @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; }
- @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; }
- @computed get unStrokd() { return this.selectedDoc?.color ? true : false; }
- @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
- @computed get markScal() { return Number(this.getField("strokeMakerScale") || "1"); }
- @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
- @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
- set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+ );
+ }
+
+ @computed get solidStk() {
+ return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === '0') ? true : false;
+ }
+ @computed get dashdStk() {
+ return this.selectedDoc?.strokeDash || '';
+ }
+ @computed get unStrokd() {
+ return this.selectedDoc?.color ? true : false;
+ }
+ @computed get widthStk() {
+ return this.getField('strokeWidth') || '1';
+ }
+ @computed get markScal() {
+ return Number(this.getField('strokeMakerScale') || '1');
+ }
+ @computed get markHead() {
+ return this.getField('strokeStartMarker') || '';
+ }
+ @computed get markTail() {
+ return this.getField('strokeEndMarker') || '';
+ }
+ set solidStk(value) {
+ this.dashdStk = '';
+ this.unStrokd = !value;
+ }
set dashdStk(value) {
value && (this._lastDash = value) && (this.unStrokd = false);
this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined);
}
- set markScal(value) { this.selectedDoc && (this.selectedDoc.strokeMarkerScale = Number(value)); }
- set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); }
- set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
- set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); }
- set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); }
-
-
- @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); }
- @computed get markScaleInput() { return this.regInput("scale", this.markScal.toString(), (val: string) => this.markScal = Number(val)); }
+ set markScal(value) {
+ this.selectedDoc && (this.selectedDoc.strokeMarkerScale = Number(value));
+ }
+ set widthStk(value) {
+ this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value));
+ }
+ set unStrokd(value) {
+ this.colorStk = value ? '' : this._lastLine;
+ }
+ set markHead(value) {
+ this.selectedDoc && (this.selectedDoc.strokeStartMarker = value);
+ }
+ set markTail(value) {
+ this.selectedDoc && (this.selectedDoc.strokeEndMarker = value);
+ }
+ @computed get stkInput() {
+ return this.regInput('stk', this.widthStk, (val: string) => (this.widthStk = val));
+ }
+ @computed get markScaleInput() {
+ return this.regInput('scale', this.markScal.toString(), (val: string) => (this.markScal = Number(val)));
+ }
regInput = (key: string, value: any, setter: (val: string) => {}) => {
- return <div className="inputBox">
- <input className="inputBox-input"
- type="text" value={value}
- onChange={e => setter(e.target.value)} />
- <div className="inputBox-button">
- <div className="inputBox-button-up" key="up2"
- onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
- <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
- </div>
- <div className="inputbox-Button-down" key="down2"
- onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
- <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ return (
+ <div className="inputBox">
+ <input className="inputBox-input" type="text" value={value} onChange={e => setter(e.target.value)} />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
+ <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+ <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ </div>
</div>
</div>
- </div>;
- }
+ );
+ };
@computed get widthAndDash() {
- return <div className="widthAndDash">
- <div className="width">
- <div className="width-top">
- <div className="width-title">Width:</div>
- <div className="width-input">{this.stkInput}</div>
- </div>
- <input className="width-range" type="range"
- defaultValue={Number(this.widthStk)} min={1} max={100}
- onChange={(action((e) => this.widthStk = e.target.value))}
- onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }}
- onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }}
- />
- </div>
-
- <div className="arrows">
- <div className="arrows-head">
+ return (
+ <div className="widthAndDash">
+ <div className="width">
<div className="width-top">
- <div className="width-title">Arrow Scale:</div>
- {/* <div className="width-input">{this.markScalInput}</div> */}
+ <div className="width-title">Width:</div>
+ <div className="width-input">{this.stkInput}</div>
</div>
- <input className="width-range" type="range"
- defaultValue={this.markScal} min={0} max={10}
- onChange={(action(e => this.markScal = +e.target.value))}
- onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("scale undo"); }}
- onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }}
+ <input
+ className="width-range"
+ type="range"
+ defaultValue={Number(this.widthStk)}
+ min={1}
+ max={100}
+ onChange={action(e => (this.widthStk = e.target.value))}
+ onMouseDown={e => {
+ this._widthUndo = UndoManager.StartBatch('width undo');
+ }}
+ onMouseUp={e => {
+ this._widthUndo?.end();
+ this._widthUndo = undefined;
+ }}
/>
</div>
- <div className="arrows-head">
- <div className="arrows-head-title" >Arrow Head: </div>
- <input key="markHead" className="arrows-head-input" type="checkbox"
- checked={this.markHead !== ""}
- onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
+
+ <div className="arrows">
+ <div className="arrows-head">
+ <div className="width-top">
+ <div className="width-title">Arrow Scale:</div>
+ {/* <div className="width-input">{this.markScalInput}</div> */}
+ </div>
+ <input
+ className="width-range"
+ type="range"
+ defaultValue={this.markScal}
+ min={0}
+ max={10}
+ onChange={action(e => (this.markScal = +e.target.value))}
+ onMouseDown={e => {
+ this._widthUndo = UndoManager.StartBatch('scale undo');
+ }}
+ onMouseUp={e => {
+ this._widthUndo?.end();
+ this._widthUndo = undefined;
+ }}
+ />
+ </div>
+ <div className="arrows-head">
+ <div className="arrows-head-title">Arrow Head: </div>
+ <input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} />
+ </div>
+ <div className="arrows-tail">
+ <div className="arrows-tail-title">Arrow End: </div>
+ <input key="markTail" className="arrows-tail-input" type="checkbox" checked={this.markTail !== ''} onChange={undoBatch(action(() => (this.markTail = this.markTail ? '' : 'arrow')))} />
+ </div>
</div>
- <div className="arrows-tail">
- <div className="arrows-tail-title" >Arrow End: </div>
- <input key="markTail" className="arrows-tail-input" type="checkbox"
- checked={this.markTail !== ""}
- onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
+ <div className="dashed">
+ <div className="dashed-title">Dashed Line:</div>
+ <input key="markHead" className="dashed-input" type="checkbox" checked={this.dashdStk === '2'} onChange={this.changeDash} />
</div>
</div>
- <div className="dashed">
- <div className="dashed-title">Dashed Line:</div>
- <input key="markHead" className="dashed-input"
- type="checkbox" checked={this.dashdStk === "2"}
- onChange={this.changeDash} />
- </div>
- </div>;
+ );
}
- @undoBatch @action
+ @undoBatch
+ @action
changeDash = () => {
- this.dashdStk = this.dashdStk === "2" ? "0" : "2";
- }
+ this.dashdStk = this.dashdStk === '2' ? '0' : '2';
+ };
@computed get appearanceEditor() {
- return <div className="appearance-editor">
- {this.widthAndDash}
- {this.strokeAndFill}
- </div>;
+ return (
+ <div className="appearance-editor">
+ {this.widthAndDash}
+ {this.strokeAndFill}
+ </div>
+ );
}
@computed get transformEditor() {
- return <div className="transform-editor">
- {this.controlPointsButton}
- {this.hgtInput}
- {this.XpsInput}
- {this.rotInput}
- </div>;
+ return (
+ <div className="transform-editor">
+ {this.controlPointsButton}
+ {this.hgtInput}
+ {this.XpsInput}
+ {this.rotInput}
+ </div>
+ );
}
@computed get optionsSubMenu() {
- return <div className="propertiesView-settings" onPointerEnter={action(() => this.inOptions = true)}
- onPointerLeave={action(() => this.inOptions = false)}>
- <div className="propertiesView-settings-title"
- onPointerDown={action(() => this.openOptions = !this.openOptions)}
- style={{ backgroundColor: this.openOptions ? "black" : "" }}>
- Options
- <div className="propertiesView-settings-title-icon">
- <FontAwesomeIcon icon={this.openOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ return (
+ <div className="propertiesView-settings" onPointerEnter={action(() => (this.inOptions = true))} onPointerLeave={action(() => (this.inOptions = false))}>
+ <div className="propertiesView-settings-title" onPointerDown={action(() => (this.openOptions = !this.openOptions))} style={{ backgroundColor: this.openOptions ? 'black' : '' }}>
+ Options
+ <div className="propertiesView-settings-title-icon">
+ <FontAwesomeIcon icon={this.openOptions ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
</div>
+ {!this.openOptions ? null : (
+ <div className="propertiesView-settings-content">
+ <PropertiesButtons />
+ </div>
+ )}
</div>
- {!this.openOptions ? (null) :
- <div className="propertiesView-settings-content">
- <PropertiesButtons />
- </div>}
- </div>;
+ );
}
@computed get sharingSubMenu() {
- return <div className="propertiesView-sharing">
- <div className="propertiesView-sharing-title"
- onPointerDown={action(() => this.openSharing = !this.openSharing)}
- style={{ backgroundColor: this.openSharing ? "black" : "" }}>
- Sharing {"&"} Permissions
- <div className="propertiesView-sharing-title-icon">
- <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+ return (
+ <div className="propertiesView-sharing">
+ <div className="propertiesView-sharing-title" onPointerDown={action(() => (this.openSharing = !this.openSharing))} style={{ backgroundColor: this.openSharing ? 'black' : '' }}>
+ Sharing {'&'} Permissions
+ <div className="propertiesView-sharing-title-icon">
+ <FontAwesomeIcon icon={this.openSharing ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
</div>
- </div>
- {!this.openSharing ? (null) :
- <div className="propertiesView-sharing-content">
- <div className="propertiesView-buttonContainer">
- {!Doc.UserDoc().noviceMode ? (<div className="propertiesView-acls-checkbox">
- <Checkbox
- color="primary"
- onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)}
- checked={this.layoutDocAcls}
- />
- <div className="propertiesView-acls-checkbox-text">Layout</div>
- </div>) : (null)}
- {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
+ {!this.openSharing ? null : (
+ <div className="propertiesView-sharing-content">
+ <div className="propertiesView-buttonContainer">
+ {!Doc.noviceMode ? (
+ <div className="propertiesView-acls-checkbox">
+ <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
+ <div className="propertiesView-acls-checkbox-text">Layout</div>
+ </div>
+ ) : null}
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
<button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
<FontAwesomeIcon icon="redo-alt" color="white" size="1x" />
</button>
</Tooltip> */}
+ </div>
+ {this.sharingTable}
</div>
- {this.sharingTable}
- </div>}
- </div>;
+ )}
+ </div>
+ );
}
/**
@@ -925,57 +1148,58 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
* If it doesn't exist, it creates it.
*/
checkFilterDoc() {
- if (!this.selectedDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.selectedDoc);
+ if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = FilterBox.createFilterDoc();
}
/**
- * Creates a new currentFilter for this.filterDoc,
+ * Creates a new currentFilter for this.filterDoc,
*/
createNewFilterDoc = () => {
- const currentDocFilters = this.selectedDoc._docFilters;
- const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
- this.selectedDoc._docFilters = new List<string>();
- this.selectedDoc._docRangeFilters = new List<string>();
- (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
- (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
- this.selectedDoc.currentFilter = undefined;
- CurrentUserUtils.setupFilterDocs(this.selectedDoc);
- }
+ if (this.selectedDoc) {
+ const currentDocFilters = this.selectedDoc._docFilters;
+ const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
+ this.selectedDoc._docFilters = new List<string>();
+ this.selectedDoc._docRangeFilters = new List<string>();
+ (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
+ (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
+ this.selectedDoc.currentFilter = FilterBox.createFilterDoc();
+ }
+ };
/**
* Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter
*/
updateFilterDoc = (doc: Doc) => {
- if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc
- const savedDocFilters = doc._docFiltersList;
- const currentDocFilters = this.selectedDoc._docFilters;
- this.selectedDoc._docFilters = new List<string>();
- (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
- this.selectedDoc.currentFilter = doc;
- doc._docFiltersList = new List<string>();
- this.selectedDoc._docFilters = savedDocFilters;
-
- const savedDocRangeFilters = doc._docRangeFiltersList;
- const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
- this.selectedDoc._docRangeFilters = new List<string>();
- (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
- this.selectedDoc.currentFilter = doc;
- doc._docRangeFiltersList = new List<string>();
- this.selectedDoc._docRangeFilters = savedDocRangeFilters;
- }
+ if (this.selectedDoc) {
+ if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc
+ const savedDocFilters = doc._docFiltersList;
+ const currentDocFilters = this.selectedDoc._docFilters;
+ this.selectedDoc._docFilters = new List<string>();
+ (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters;
+ this.selectedDoc.currentFilter = doc;
+ doc._docFiltersList = new List<string>();
+ this.selectedDoc._docFilters = savedDocFilters;
+
+ const savedDocRangeFilters = doc._docRangeFiltersList;
+ const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
+ this.selectedDoc._docRangeFilters = new List<string>();
+ (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters;
+ this.selectedDoc.currentFilter = doc;
+ doc._docRangeFiltersList = new List<string>();
+ this.selectedDoc._docRangeFilters = savedDocRangeFilters;
+ }
+ };
@computed get filtersSubMenu() {
- return !(this.selectedDoc?.currentFilter instanceof Doc) ? (null) : <div className="propertiesView-filters">
- <div className="propertiesView-filters-title"
- onPointerDown={action(() => this.openFilters = !this.openFilters)}
- style={{ backgroundColor: this.openFilters ? "black" : "" }}>
- Filters
- <div className="propertiesView-filters-title-icon">
- <FontAwesomeIcon icon={this.openFilters ? "caret-down" : "caret-right"} size="lg" color="white" />
+ return !(this.selectedDoc?.currentFilter instanceof Doc) ? null : (
+ <div className="propertiesView-filters">
+ <div className="propertiesView-filters-title" onPointerDown={action(() => (this.openFilters = !this.openFilters))} style={{ backgroundColor: this.openFilters ? 'black' : '' }}>
+ Filters
+ <div className="propertiesView-filters-title-icon">
+ <FontAwesomeIcon icon={this.openFilters ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
</div>
- </div>
- {
- !this.openFilters ? (null) :
+ {!this.openFilters ? null : (
<div className="propertiesView-filters-content" style={{ height: this.selectedDoc.currentFilter[HeightSym]() + 15 }}>
<DocumentView
Document={this.selectedDoc.currentFilter}
@@ -1003,100 +1227,109 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
createNewFilterDoc={this.createNewFilterDoc}
updateFilterDoc={this.updateFilterDoc}
docViewPath={returnEmptyDoclist}
- layerProvider={undefined}
dontCenter="y"
/>
</div>
- }
- </div >;
+ )}
+ </div>
+ );
}
@computed get inkSubMenu() {
- return <>
- {!this.isInk ? (null) :
- <div className="propertiesView-appearance">
- <div className="propertiesView-appearance-title"
- onPointerDown={action(() => this.openAppearance = !this.openAppearance)}
- style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
- Appearance
- <div className="propertiesView-appearance-title-icon">
- <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" />
+ return (
+ <>
+ {!this.isInk ? null : (
+ <div className="propertiesView-appearance">
+ <div className="propertiesView-appearance-title" onPointerDown={action(() => (this.openAppearance = !this.openAppearance))} style={{ backgroundColor: this.openAppearance ? 'black' : '' }}>
+ Appearance
+ <div className="propertiesView-appearance-title-icon">
+ <FontAwesomeIcon icon={this.openAppearance ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
</div>
+ {!this.openAppearance ? null : <div className="propertiesView-appearance-content">{this.appearanceEditor}</div>}
</div>
- {!this.openAppearance ? (null) :
- <div className="propertiesView-appearance-content">
- {this.appearanceEditor}
- </div>}
- </div>}
-
- {this.isInk ? <div className="propertiesView-transform">
- <div className="propertiesView-transform-title"
- onPointerDown={action(() => this.openTransform = !this.openTransform)}
- style={{ backgroundColor: this.openTransform ? "black" : "" }}>
- Transform
- <div className="propertiesView-transform-title-icon">
- <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" />
+ )}
+
+ {this.isInk ? (
+ <div className="propertiesView-transform">
+ <div className="propertiesView-transform-title" onPointerDown={action(() => (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}>
+ Transform
+ <div className="propertiesView-transform-title-icon">
+ <FontAwesomeIcon icon={this.openTransform ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openTransform ? <div className="propertiesView-transform-content">{this.transformEditor}</div> : null}
</div>
- </div>
- {this.openTransform ? <div className="propertiesView-transform-content">
- {this.transformEditor}
- </div> : null}
- </div> : null}
- </>;
+ ) : null}
+ </>
+ );
}
@computed get fieldsSubMenu() {
- return <div className="propertiesView-fields">
- <div className="propertiesView-fields-title"
- onPointerDown={action(() => this.openFields = !this.openFields)}
- style={{ backgroundColor: this.openFields ? "black" : "" }}>
- Fields {"&"} Tags
- <div className="propertiesView-fields-title-icon">
- <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
+ return (
+ <div className="propertiesView-fields">
+ <div className="propertiesView-fields-title" onPointerDown={action(() => (this.openFields = !this.openFields))} style={{ backgroundColor: this.openFields ? 'black' : '' }}>
+ Fields {'&'} Tags
+ <div className="propertiesView-fields-title-icon">
+ <FontAwesomeIcon icon={this.openFields ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
</div>
+ {!Doc.noviceMode && this.openFields ? (
+ <div className="propertiesView-fields-checkbox">
+ {this.fieldsCheckbox}
+ <div className="propertiesView-fields-checkbox-text">Layout</div>
+ </div>
+ ) : null}
+ {!this.openFields ? null : <div className="propertiesView-fields-content">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>}
</div>
- {!Doc.UserDoc().noviceMode && this.openFields ? <div className="propertiesView-fields-checkbox">
- {this.fieldsCheckbox}
- <div className="propertiesView-fields-checkbox-text">Layout</div>
- </div> : null}
- {!this.openFields ? (null) :
- <div className="propertiesView-fields-content">
- {Doc.UserDoc().noviceMode ? this.noviceFields : this.expandedField}
- </div>}
- </div>;
+ );
}
@computed get contextsSubMenu() {
- return <div className="propertiesView-contexts">
- <div className="propertiesView-contexts-title"
- onPointerDown={action(() => this.openContexts = !this.openContexts)}
- style={{ backgroundColor: this.openContexts ? "black" : "" }}>
- Contexts
- <div className="propertiesView-contexts-title-icon">
- <FontAwesomeIcon icon={this.openContexts ? "caret-down" : "caret-right"} size="lg" color="white" />
+ return (
+ <div className="propertiesView-contexts">
+ <div className="propertiesView-contexts-title" onPointerDown={action(() => (this.openContexts = !this.openContexts))} style={{ backgroundColor: this.openContexts ? 'black' : '' }}>
+ Other Contexts
+ <div className="propertiesView-contexts-title-icon">
+ <FontAwesomeIcon icon={this.openContexts ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
</div>
+ {this.openContexts ? <div className="propertiesView-contexts-content">{this.contexts}</div> : null}
</div>
- {this.openContexts ? <div className="propertiesView-contexts-content" >{this.contexts}</div> : null}
- </div>;
+ );
+ }
+
+ @computed get linksSubMenu() {
+ return (
+ <div className="propertiesView-contexts">
+ <div className="propertiesView-contexts-title" onPointerDown={action(() => (this.openLinks = !this.openLinks))} style={{ backgroundColor: this.openLinks ? 'black' : '' }}>
+ Linked To
+ <div className="propertiesView-contexts-title-icon">
+ <FontAwesomeIcon icon={this.openLinks ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openLinks ? <div className="propertiesView-contexts-content">{this.links}</div> : null}
+ </div>
+ );
}
@computed get layoutSubMenu() {
- return <div className="propertiesView-layout">
- <div className="propertiesView-layout-title"
- onPointerDown={action(() => this.openLayout = !this.openLayout)}
- style={{ backgroundColor: this.openLayout ? "black" : "" }}>
- Layout
- <div className="propertiesView-layout-title-icon">
- <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
+ return (
+ <div className="propertiesView-layout">
+ <div className="propertiesView-layout-title" onPointerDown={action(() => (this.openLayout = !this.openLayout))} style={{ backgroundColor: this.openLayout ? 'black' : '' }}>
+ Layout
+ <div className="propertiesView-layout-title-icon">
+ <FontAwesomeIcon icon={this.openLayout ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
</div>
+ {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null}
</div>
- {this.openLayout ? <div className="propertiesView-layout-content" >{this.layoutPreview}</div> : null}
- </div>;
+ );
}
@observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
@observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
- @observable private relationshipButtonColor: string = "";
+ @observable private relationshipButtonColor: string = '';
// @action
// handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
@@ -1104,7 +1337,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@undoBatch
handleDescriptionChange = action((value: string) => {
- if (LinkManager.currentLink) {
+ if (LinkManager.currentLink && this.selectedDoc) {
this.selectedDoc.description = value;
this.description = value;
return true;
@@ -1113,7 +1346,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@undoBatch
handleLinkRelationshipChange = action((value: string) => {
- if (LinkManager.currentLink) {
+ if (LinkManager.currentLink && this.selectedDoc) {
this.selectedDoc.linkRelationship = value;
this.relationship = value;
return true;
@@ -1138,11 +1371,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes);
const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
- // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
+ // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
if (!linkRelationshipList?.includes(value)) {
linkRelationshipList.push(value);
linkRelationshipSizes.push(1);
- const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")";
+ const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')';
linkColorList.push(randColor);
// if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
} else if (linkRelationshipList && value !== prevRelationship) {
@@ -1150,27 +1383,29 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
//increment size of new relationship size
if (index !== -1 && index < linkRelationshipSizes.length) {
const pvalue = linkRelationshipSizes[index];
- linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1);
+ linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1;
}
//decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
if (linkRelationshipList.includes(prevRelationship)) {
const pindex = linkRelationshipList.indexOf(prevRelationship);
if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
const pvalue = linkRelationshipSizes[pindex];
- linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1));
+ linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1);
}
}
-
}
- this.relationshipButtonColor = "rgb(62, 133, 55)";
- setTimeout(action(() => this.relationshipButtonColor = ""), 750);
+ this.relationshipButtonColor = 'rgb(62, 133, 55)';
+ setTimeout(
+ action(() => (this.relationshipButtonColor = '')),
+ 750
+ );
return true;
}
});
@undoBatch
changeFollowBehavior = action((follow: string) => {
- if (LinkManager.currentLink) {
+ if (LinkManager.currentLink && this.selectedDoc) {
this.selectedDoc.followLinkLocation = follow;
return true;
}
@@ -1179,61 +1414,72 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
onSelectOutDesc = () => {
this.setDescripValue(this.description);
document.getElementById('link_description_input')?.blur();
- }
+ };
onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
this.setDescripValue(this.description);
document.getElementById('link_description_input')?.blur();
}
- }
+ };
onSelectOutRelationship = () => {
this.setLinkRelationshipValue(this.relationship);
document.getElementById('link_relationship_input')?.blur();
- }
+ };
onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
this.setLinkRelationshipValue(this.relationship);
document.getElementById('link_relationship_input')?.blur();
}
- }
+ };
toggleAnchor = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove)));
- }
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove))));
+ };
toggleArrow = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)));
- }
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow))));
+ };
+
+ toggleZoomToTarget1 = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor1).followLinkZoom = !DocCast(this.selectedDoc.anchor1).followLinkZoom))));
+ };
+ toggleZoomToTarget2 = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor2).followLinkZoom = !DocCast(this.selectedDoc.anchor2).followLinkZoom))));
+ };
@computed
get editRelationship() {
- return <input
- autoComplete={"off"}
- id="link_relationship_input"
- value={StrCast(this.selectedDoc.linkRelationship)}
- onKeyDown={this.onRelationshipKey}
- onBlur={this.onSelectOutRelationship}
- onChange={e => this.handleLinkRelationshipChange(e.currentTarget.value)}
- className="text"
- type="text"
- />;
+ return (
+ <input
+ autoComplete={'off'}
+ id="link_relationship_input"
+ value={StrCast(this.selectedDoc?.linkRelationship)}
+ onKeyDown={this.onRelationshipKey}
+ onBlur={this.onSelectOutRelationship}
+ onChange={e => this.handleLinkRelationshipChange(e.currentTarget.value)}
+ className="text"
+ type="text"
+ />
+ );
}
@computed
get editDescription() {
- return <input
- autoComplete={"off"}
- id="link_description_input"
- value={StrCast(this.selectedDoc.description)}
- onKeyDown={this.onDescriptionKey}
- onBlur={this.onSelectOutDesc}
- onChange={e => this.handleDescriptionChange(e.currentTarget.value)}
- className="text"
- type="text"
- />;
+ return (
+ <input
+ autoComplete={'off'}
+ id="link_description_input"
+ value={StrCast(this.selectedDoc?.description)}
+ onKeyDown={this.onDescriptionKey}
+ onBlur={this.onSelectOutDesc}
+ onChange={e => this.handleDescriptionChange(e.currentTarget.value)}
+ className="text"
+ type="text"
+ />
+ );
}
/**
@@ -1248,103 +1494,122 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
// }
render() {
- const isNovice = BoolCast(Doc.UserDoc().noviceMode);
+ const isNovice = Doc.noviceMode;
if (!this.selectedDoc && !this.isPres) {
- return <div className="propertiesView" style={{ width: this.props.width }}>
- <div className="propertiesView-title" style={{ width: this.props.width }}>
- No Document Selected
+ return (
+ <div className="propertiesView" style={{ width: this.props.width }}>
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ No Document Selected
+ </div>
</div>
- </div>;
-
+ );
} else {
if (this.selectedDoc && this.isLink) {
- return <div className="propertiesView">
- <div className="propertiesView-title">
- Linking
- </div>
- <div className="propertiesView-section">
- <p className="propertiesView-label">Information</p>
- <div className="propertiesView-input first" id="propertiesView-category">
- <p>Link Relationship</p>
- {this.editRelationship}
- </div>
- <div className="propertiesView-input" id="propertiesView-description">
- <p>Description</p>
- {this.editDescription}
- </div>
- </div>
- <div className="propertiesView-section">
- <p className="propertiesView-label">Behavior</p>
- <div className="propertiesView-input inline first" id="propertiesView-follow">
- <p>Follow</p>
- <select
- name="selectList"
- id="selectList"
- onChange={e => this.changeFollowBehavior(e.currentTarget.value)}
- value={StrCast(this.selectedDoc.followLinkLocation, "default")}>
- <option value="default">Default</option>
- <option value="add:left">Open in new left pane</option>
- <option value="add:right">Open in new right pane</option>
- <option value="replace:left">Replace left tab</option>
- <option value="replace:right">Replace right tab</option>
- <option value="fullScreen">Open full screen</option>
- <option value="add">Open in new tab</option>
- <option value="replace">Replace current tab</option>
- {this.selectedDoc.linksToAnnotation
- ? <option value="openExternal">Open in external page</option>
- : null}
- </select>
- </div>
- <div className="propertiesView-input inline" id="propertiesView-anchor">
- <p>Auto-move anchor</p>
- <button
- style={{ background: this.selectedDoc.hidden ? "gray" : !this.selectedDoc.linkAutoMove ? "" : "#4476f7", borderRadius: 3 }}
- onPointerDown={this.toggleAnchor} onClick={e => e.stopPropagation()}
- className="propertiesButton"
- >
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
+ return (
+ <div className="propertiesView">
+ <div className="propertiesView-title">Linking</div>
+ <div className="propertiesView-section">
+ <p className="propertiesView-label">Information</p>
+ <div className="propertiesView-input first">
+ <p>Link Relationship</p>
+ {this.editRelationship}
+ </div>
+ <div className="propertiesView-input">
+ <p>Description</p>
+ {this.editDescription}
+ </div>
</div>
- <div className="propertiesView-input inline" id="propertiesView-displayArrow">
- <p>Display arrow</p>
- <button
- style={{ background: this.selectedDoc.hidden ? "gray" : !this.selectedDoc.displayArrow ? "" : "#4476f7", borderRadius: 3 }}
- onPointerDown={this.toggleArrow} onClick={e => e.stopPropagation()}
- className="propertiesButton"
- >
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
+ <div className="propertiesView-section">
+ <p className="propertiesView-label">Behavior</p>
+ <div className="propertiesView-input inline first">
+ <p>Follow</p>
+ <select name="selectList" id="selectList" onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.selectedDoc.followLinkLocation, 'default')}>
+ <option value="default">Default</option>
+ <option value="add:left">Open in new left pane</option>
+ <option value="add:right">Open in new right pane</option>
+ <option value="replace:left">Replace left tab</option>
+ <option value="replace:right">Replace right tab</option>
+ <option value="fullScreen">Open full screen</option>
+ <option value="add">Open in new tab</option>
+ <option value="replace">Replace current tab</option>
+ {this.selectedDoc.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
+ </select>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Auto-move anchor</p>
+ <button
+ style={{ background: this.selectedDoc.hidden ? 'gray' : !this.selectedDoc.linkAutoMove ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={this.toggleAnchor}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Display arrow</p>
+ <button
+ style={{ background: this.selectedDoc.hidden ? 'gray' : !this.selectedDoc.displayArrow ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={this.toggleArrow}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Zoom to target</p>
+ <button
+ style={{ background: this.selectedDoc.hidden ? 'gray' : !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={this.toggleZoomToTarget1}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Zoom to source</p>
+ <button
+ style={{ background: this.selectedDoc.hidden ? 'gray' : !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={this.toggleZoomToTarget2}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
</div>
</div>
- </div >;
+ );
}
if (this.selectedDoc && !this.isPres) {
- return <div className="propertiesView" style={{
- width: this.props.width,
- minWidth: this.props.width,
- //overflowY: this.scrolling ? "scroll" : "visible"
- }} >
- <div className="propertiesView-title" style={{ width: this.props.width }}>
- Properties
- </div>
- <div className="propertiesView-name">
- {this.editableTitle}
- </div>
+ return (
+ <div
+ className="propertiesView"
+ style={{
+ width: this.props.width,
+ minWidth: this.props.width,
+ //overflowY: this.scrolling ? "scroll" : "visible"
+ }}>
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ Properties
+ </div>
+ <div className="propertiesView-name">{this.editableTitle}</div>
+
+ {this.contextsSubMenu}
- {this.inkSubMenu}
+ {this.linksSubMenu}
- {this.optionsSubMenu}
+ {this.inkSubMenu}
- {this.sharingSubMenu}
+ {this.optionsSubMenu}
- {isNovice ? null : this.filtersSubMenu}
+ {this.fieldsSubMenu}
- {isNovice ? null : this.fieldsSubMenu}
+ {isNovice ? null : this.sharingSubMenu}
- {isNovice ? null : this.contextsSubMenu}
+ {isNovice ? null : this.filtersSubMenu}
- {isNovice ? null : this.layoutSubMenu}
- </div>;
+ {isNovice ? null : this.layoutSubMenu}
+ </div>
+ );
}
if (this.isPres) {
const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0;
@@ -1352,35 +1617,35 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const viewType = PresBox.Instance.activeItem?._viewType;
const pannable: boolean = (type === DocumentType.COL && viewType === CollectionViewType.Freeform) || type === DocumentType.IMG;
const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking || viewType === CollectionViewType.NoteTaking;
- return <div className="propertiesView" style={{ width: this.props.width }}>
- <div className="propertiesView-title" style={{ width: this.props.width }}>
- Presentation
- </div>
- <div className="propertiesView-name" style={{ borderBottom: 0 }}>
- {this.editableTitle}
- <div className="propertiesView-presSelected">
- <div className="propertiesView-selectedCount">
- {PresBox.Instance?._selectedArray.size} selected
- </div>
- <div className="propertiesView-selectedList">
- {PresBox.Instance?.listOfSelected}
- </div>
+ return (
+ <div className="propertiesView" style={{ width: this.props.width }}>
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ Presentation
</div>
- </div>
- {!selectedItem ? (null) : <div className="propertiesView-presTrails">
- <div className="propertiesView-presTrails-title"
- onPointerDown={action(() => { this.openPresTransitions = !this.openPresTransitions; })}
- style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: "center" }} icon={"rocket"} /> &nbsp; Transitions
- <div className="propertiesView-presTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ <div className="propertiesView-name" style={{ borderBottom: 0 }}>
+ {this.editableTitle}
+ <div className="propertiesView-presSelected">
+ <div className="propertiesView-selectedCount">{PresBox.Instance?._selectedArray.size} selected</div>
+ <div className="propertiesView-selectedList">{PresBox.Instance?.listOfSelected}</div>
</div>
</div>
- {this.openPresTransitions ? <div className="propertiesView-presTrails-content">
- {PresBox.Instance.transitionDropdown}
- </div> : null}
- </div>}
- {/* {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) : <div className="propertiesView-presTrails">
+ {!selectedItem ? null : (
+ <div className="propertiesView-presTrails">
+ <div
+ className="propertiesView-presTrails-title"
+ onPointerDown={action(() => {
+ this.openPresTransitions = !this.openPresTransitions;
+ })}
+ style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Transitions
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openPresTransitions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+ </div>
+ )}
+ {/* {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) : <div className="propertiesView-presTrails">
<div className="propertiesView-presTrails-title"
onPointerDown={action(() => this.openPresProgressivize = !this.openPresProgressivize)}
style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}>
@@ -1393,20 +1658,23 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{PresBox.Instance.progressivizeDropdown}
</div> : null}
</div>} */}
- {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) : <div className="propertiesView-presTrails">
- <div className="propertiesView-presTrails-title"
- onPointerDown={action(() => { this.openSlideOptions = !this.openSlideOptions; })}
- style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: "center" }} icon={type === DocumentType.AUDIO ? "file-audio" : "file-video"} /> &nbsp; {type === DocumentType.AUDIO ? "Audio Options" : "Video Options"}
- <div className="propertiesView-presTrails-title-icon">
- <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
+ <div className="propertiesView-presTrails">
+ <div
+ className="propertiesView-presTrails-title"
+ onPointerDown={action(() => {
+ this.openSlideOptions = !this.openSlideOptions;
+ })}
+ style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}>
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openSlideOptions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
</div>
- </div>
- {this.openSlideOptions ? <div className="propertiesView-presTrails-content">
- {PresBox.Instance.mediaOptionsDropdown}
- </div> : null}
- </div>}
- {/* <div className="propertiesView-presTrails">
+ )}
+ {/* <div className="propertiesView-presTrails">
<div className="propertiesView-presTrails-title"
onPointerDown={action(() => { this.openAddSlide = !this.openAddSlide; })}
style={{ backgroundColor: this.openAddSlide ? "black" : "" }}>
@@ -1419,8 +1687,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{PresBox.Instance.newDocumentDropdown}
</div> : null}
</div> */}
- </div>;
+ </div>
+ );
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index b7ea124b9..416162aeb 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,17 +1,16 @@
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc, Opt } from "../../fields/Doc";
-import { ScriptField } from "../../fields/ScriptField";
-import { ScriptCast } from "../../fields/Types";
-import { emptyFunction } from "../../Utils";
-import { DragManager } from "../util/DragManager";
-import { CompileScript } from "../util/Scripting";
-import { EditableView } from "./EditableView";
-import { DocumentIconContainer } from "./nodes/DocumentIcon";
-import { OverlayView } from "./OverlayView";
-import "./ScriptBox.scss";
-
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, Opt } from '../../fields/Doc';
+import { ScriptField } from '../../fields/ScriptField';
+import { ScriptCast } from '../../fields/Types';
+import { emptyFunction } from '../../Utils';
+import { DragManager } from '../util/DragManager';
+import { CompileScript } from '../util/Scripting';
+import { EditableView } from './EditableView';
+import { DocumentIconContainer } from './nodes/DocumentIcon';
+import { OverlayView } from './OverlayView';
+import './ScriptBox.scss';
export interface ScriptBoxProps {
onSave: (text: string, onError: (error: string) => void) => void;
@@ -28,53 +27,58 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
constructor(props: ScriptBoxProps) {
super(props);
- this._scriptText = props.initialText || "";
+ this._scriptText = props.initialText || '';
}
@action
onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
this._scriptText = e.target.value;
- }
+ };
@action
onError = (error: string) => {
- console.log("ScriptBox: " + error);
- }
+ console.log('ScriptBox: ' + error);
+ };
overlayDisposer?: () => void;
onFocus = () => {
this.overlayDisposer?.();
this.overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
- }
+ };
onBlur = () => {
this.overlayDisposer?.();
- }
+ };
render() {
- let onFocus: Opt<() => void> = undefined, onBlur: Opt<() => void> = undefined;
+ let onFocus: Opt<() => void> = undefined,
+ onBlur: Opt<() => void> = undefined;
if (this.props.showDocumentIcons) {
onFocus = this.onFocus;
onBlur = this.onBlur;
}
- const params = <EditableView
- contents={""}
- display={"block"}
- maxHeight={72}
- height={35}
- fontSize={28}
- GetValue={() => ""}
- SetValue={(value: string) => this.props.setParams && this.props.setParams(value.split(" ").filter(s => s !== " ")) ? true : true}
- />;
+ const params = <EditableView contents={''} display={'block'} maxHeight={72} height={35} fontSize={28} GetValue={() => ''} SetValue={(value: string) => (this.props.setParams?.(value.split(' ').filter(s => s !== ' ')) ? true : true)} />;
return (
<div className="scriptBox-outerDiv">
- <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea>
- <div style={{ background: "beige" }} >{params}</div>
+ <div style={{ background: 'beige' }}>{params}</div>
</div>
<div className="scriptBox-toolbar">
- <button onClick={e => { this.props.onSave(this._scriptText, this.onError); e.stopPropagation(); }}>Save</button>
- <button onClick={e => { this.props.onCancel && this.props.onCancel(); e.stopPropagation(); }}>Cancel</button>
+ <button
+ onClick={e => {
+ this.props.onSave(this._scriptText, this.onError);
+ e.stopPropagation();
+ }}>
+ Save
+ </button>
+ <button
+ onClick={e => {
+ this.props.onCancel && this.props.onCancel();
+ e.stopPropagation();
+ }}>
+ Cancel
+ </button>
</div>
</div>
);
@@ -90,35 +94,43 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
// tslint:disable-next-line: no-unnecessary-callback-wrapper
const params: string[] = [];
const setParams = (p: string[]) => params.splice(0, params.length, ...p);
- const scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={overlayDisposer} onSave={(text, onError) => {
- if (!text) {
- Doc.GetProto(doc)[fieldKey] = undefined;
- } else {
- const script = CompileScript(text, {
- params: { this: Doc.name, ...contextParams },
- typecheck: false,
- editable: true,
- transformer: DocumentIconContainer.getTransformer()
- });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- return;
- }
+ const scriptingBox = (
+ <ScriptBox
+ initialText={originalText}
+ setParams={setParams}
+ onCancel={overlayDisposer}
+ onSave={(text, onError) => {
+ if (!text) {
+ Doc.GetProto(doc)[fieldKey] = undefined;
+ } else {
+ const script = CompileScript(text, {
+ params: { this: Doc.name, ...contextParams },
+ typecheck: false,
+ editable: true,
+ transformer: DocumentIconContainer.getTransformer(),
+ });
+ if (!script.compiled) {
+ onError(script.errors.map(error => error.messageText).join('\n'));
+ return;
+ }
- const div = document.createElement("div");
- div.style.width = "90";
- div.style.height = "20";
- div.style.background = "gray";
- div.style.position = "absolute";
- div.style.display = "inline-block";
- div.style.transform = `translate(${clientX}px, ${clientY}px)`;
- div.innerHTML = "button";
- params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY);
+ const div = document.createElement('div');
+ div.style.width = '90';
+ div.style.height = '20';
+ div.style.background = 'gray';
+ div.style.position = 'absolute';
+ div.style.display = 'inline-block';
+ div.style.transform = `translate(${clientX}px, ${clientY}px)`;
+ div.innerHTML = 'button';
+ params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY);
- Doc.GetProto(doc)[fieldKey] = new ScriptField(script);
- overlayDisposer();
- }
- }} showDocumentIcons />;
+ Doc.GetProto(doc)[fieldKey] = new ScriptField(script);
+ overlayDisposer();
+ }
+ }}
+ showDocumentIcons
+ />
+ );
overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title });
}
}
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 4ed6da24a..4fecfa4d9 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -123,8 +123,9 @@ export class ScriptingRepl extends React.Component {
let stopProp = true;
switch (e.key) {
case "Enter": {
+ e.stopPropagation();
const docGlobals: { [name: string]: any } = {};
- DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`d${i}`] = dv.props.Document);
+ Array.from(DocumentManager.Instance.DocumentViews).forEach((dv, i) => docGlobals[`d${i}`] = dv.props.Document);
const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
@@ -212,7 +213,7 @@ export class ScriptingRepl extends React.Component {
}
onBlur = () => {
- this.overlayDisposer && this.overlayDisposer();
+ this.overlayDisposer?.();
}
render() {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 62f6e388f..1c14c7cd5 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,22 +1,20 @@
import { computed } from 'mobx';
-import { observer } from "mobx-react";
-import { Doc, DocListCast, StrListCast, Opt } from "../../fields/Doc";
+import { observer } from 'mobx-react';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, OmitKeys, returnOne, returnTrue, returnZero } from '../../Utils';
+import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { Transform } from '../util/Transform';
import { CollectionStackingView } from './collections/CollectionStackingView';
-import { CollectionViewType } from './collections/CollectionView';
import { FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { SearchBox } from './search/SearchBox';
-import "./SidebarAnnos.scss";
+import './SidebarAnnos.scss';
import { StyleProp } from './StyleProvider';
-import React = require("react");
-import { DocumentViewProps } from './nodes/DocumentView';
-import { DocumentType } from '../documents/DocumentTypes';
+import React = require('react');
interface ExtraProps {
fieldKey: string;
@@ -28,8 +26,8 @@ interface ExtraProps {
nativeWidth: number;
whenChildContentsActiveChanged: (isActive: boolean) => void;
ScreenToLocalTransform: () => Transform;
- sidebarAddDocument: (doc: (Doc | Doc[]), suffix: string) => boolean;
- removeDocument: (doc: (Doc | Doc[]), suffix: string) => boolean;
+ sidebarAddDocument: (doc: Doc | Doc[], suffix: string) => boolean;
+ removeDocument: (doc: Doc | Doc[], suffix: string) => boolean;
moveDocument: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string) => boolean;
}
@observer
@@ -42,29 +40,41 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
@computed get allMetadata() {
const keys = new Set<string>();
DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] !== "_" && (key[0] === key[0].toUpperCase())).sort();
+ return Array.from(keys.keys())
+ .filter(key => key[0])
+ .filter(key => key[0] !== '_' && key[0] === key[0].toUpperCase())
+ .sort();
}
@computed get allUsers() {
const keys = new Set<string>();
DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
return Array.from(keys.keys()).sort();
}
- get filtersKey() { return "_" + this.sidebarKey + "-docFilters"; }
+ get filtersKey() {
+ return '_' + this.sidebarKey + '-docFilters';
+ }
anchorMenuClick = (anchor: Doc) => {
- const startup = StrListCast(this.props.rootDoc.docFilters).map(filter => filter.split(":")[0]).join(" ");
+ const startup = StrListCast(this.props.rootDoc.docFilters)
+ .map(filter => filter.split(':')[0])
+ .join(' ');
const target = Docs.Create.TextDocument(startup, {
- title: "-note-",
- annotationOn: this.props.rootDoc, _width: 200, _height: 50, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
- _fontFamily: StrCast(Doc.UserDoc().fontFamily)
+ title: '-note-',
+ annotationOn: this.props.rootDoc,
+ _width: 200,
+ _height: 50,
+ _fitWidth: true,
+ _autoHeight: true,
+ _fontSize: StrCast(Doc.UserDoc().fontSize),
+ _fontFamily: StrCast(Doc.UserDoc().fontFamily),
});
FormattedTextBox.SelectOnLoad = target[Id];
FormattedTextBox.DontSelectInitialText = true;
- this.allMetadata.map(tag => target[tag] = tag);
- DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup", "annotation");
+ this.allMetadata.map(tag => (target[tag] = tag));
+ DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on');
this.addDocument(target);
this._stackRef.current?.focusDocument(target);
- }
+ };
makeDocUnfiltered = (doc: Doc) => {
if (DocListCast(this.props.rootDoc[this.sidebarKey]).includes(doc)) {
if (this.props.layoutDoc[this.filtersKey]) {
@@ -73,70 +83,90 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
return true;
}
return false;
- }
+ };
- get sidebarKey() { return this.props.fieldKey + "-sidebar"; }
+ get sidebarKey() {
+ return this.props.fieldKey + '-sidebar';
+ }
filtersHeight = () => 38;
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.props.dataDoc), 0).scale(this.props.scaling?.() || 1);
+ screenToLocalTransform = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .translate(Doc.NativeWidth(this.props.dataDoc), 0)
+ .scale(this.props.NativeDimScaling?.() || 1);
// panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 :
// this.props.usePanelWidth ? this.props.PanelWidth() :
// (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
- panelWidth = () => !this.props.showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP ? this.props.PanelWidth() : (NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.nativeWidth);
+ panelWidth = () =>
+ !this.props.showSidebar
+ ? 0
+ : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP
+ ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1)
+ : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth);
panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
- showTitle = () => "title";
- setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight());
+ showTitle = () => 'title';
+ setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
render() {
const renderTag = (tag: string) => {
const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
- return <div key={tag} className={`sidebarAnnos-filterTag${active ? "-active" : ""}`}
- onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, tag, "check", true, this.sidebarKey, e.shiftKey)}>
- {tag}
- </div>;
+ return (
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, tag, 'check', true, this.sidebarKey, e.shiftKey)}>
+ {tag}
+ </div>
+ );
};
const renderMeta = (tag: string) => {
const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:exists`);
- return <div key={tag} className={`sidebarAnnos-filterTag${active ? "-active" : ""}`}
- onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, tag, "exists", true, this.sidebarKey, e.shiftKey)}>
- {tag}
- </div>;
+ return (
+ <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, tag, tag, 'exists', true, this.sidebarKey, e.shiftKey)}>
+ {tag}
+ </div>
+ );
};
const renderUsers = (user: string) => {
const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`author:${user}:check`);
- return <div key={user} className={`sidebarAnnos-filterUser${active ? "-active" : ""}`}
- onClick={e => Doc.setDocFilter(this.props.rootDoc, "author", user, "check", true, this.sidebarKey, e.shiftKey)}>
- {user}
- </div>;
+ return (
+ <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, this.sidebarKey, e.shiftKey)}>
+ {user}
+ </div>
+ );
};
// TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos
- return !this.props.showSidebar ? (null) :
- <div style={{
- position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, top: this.props.rootDoc.type !== DocumentType.RTF && StrCast(this.props.rootDoc._showTitle) === "title" ? 15 : 0, right: 0,
- background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor),
- width: `${this.panelWidth()}px`,
- height: "100%"
- }}>
- <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight(), width: this.panelWidth() }}
- onWheel={e => e.stopPropagation()}>
+ return !this.props.showSidebar ? null : (
+ <div
+ style={{
+ position: 'absolute',
+ pointerEvents: this.props.isContentActive() ? 'all' : undefined,
+ top: this.props.rootDoc.type !== DocumentType.RTF && StrCast(this.props.rootDoc._showTitle) === 'title' ? 15 : 0,
+ right: 0,
+ background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor),
+ width: `100%`,
+ height: '100%',
+ }}>
+ <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
{this.allUsers.map(renderUsers)}
{this.allMetadata.map(renderMeta)}
</div>
- <div style={{ width: "100%", height: this.panelHeight(), position: "relative" }}>
- <CollectionStackingView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} ref={this._stackRef}
+ <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
+ <CollectionStackingView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ ref={this._stackRef}
NativeWidth={returnZero}
NativeHeight={returnZero}
PanelHeight={this.panelHeight}
PanelWidth={this.panelWidth}
docFilters={this.docFilters}
- scaleField={this.sidebarKey + "-scale"}
+ scaleField={this.sidebarKey + '-scale'}
setHeight={this.setHeightCallback}
isAnnotationOverlay={false}
select={emptyFunction}
- scaling={returnOne}
+ NativeDimScaling={returnOne}
childShowTitle={this.showTitle}
+ childDocumentsActive={this.props.isContentActive}
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
childHideDecorationTitle={returnTrue}
removeDocument={this.removeDocument}
@@ -147,9 +177,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
renderDepth={this.props.renderDepth + 1}
viewType={CollectionViewType.Stacking}
fieldKey={this.sidebarKey}
- pointerEvents={"all"}
+ pointerEvents={returnAll}
/>
</div>
- </div>;
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index f26ed1f2d..8929954c8 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -25,5 +25,5 @@
}
.styleProvider-treeView-icon {
- display: none;
+ opacity: 0;
} \ No newline at end of file
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index c1a088b36..3bd4f5152 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,249 +1,320 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import 'golden-layout/src/css/goldenlayout-base.css';
-import 'golden-layout/src/css/goldenlayout-dark-theme.css';
import { action, runInAction } from 'mobx';
-import { Doc, Opt, StrListCast } from "../../fields/Doc";
-import { List } from '../../fields/List';
-import { listSpec } from '../../fields/Schema';
-import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
+import { extname } from 'path';
+import { Doc, Opt } from '../../fields/Doc';
+import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types';
import { DashColor, lightOrDark } from '../../Utils';
-import { DocumentType } from '../documents/DocumentTypes';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocFocusOrOpen } from '../util/DocumentManager';
import { ColorScheme } from '../util/SettingsManager';
-import { SnappingManager } from '../util/SnappingManager';
import { undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionViewType } from './collections/CollectionView';
+import { TreeSort } from './collections/TreeView';
import { Colors } from './global/globalEnums';
+import { InkingStroke } from './InkingStroke';
import { MainView } from './MainView';
-import { DocumentViewProps } from "./nodes/DocumentView";
+import { DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
import { SliderBox } from './nodes/SliderBox';
-import "./StyleProvider.scss";
-import React = require("react");
-
-export enum StyleLayers {
- Background = "background"
-}
+import './StyleProvider.scss';
+import React = require('react');
export enum StyleProp {
- TreeViewIcon = "treeViewIcon",
- DocContents = "docContents", // when specified, the JSX returned will replace the normal rendering of the document view
- Opacity = "opacity", // opacity of the document view
- Hidden = "hidden", // whether the document view should not be isplayed
- BoxShadow = "boxShadow", // box shadow - used for making collections standout and for showing clusters in free form views
- BorderRounding = "borderRounding", // border radius of the document view
- Color = "color", // foreground color of Document view items
- BackgroundColor = "backgroundColor", // background color of a document view
- WidgetColor = "widgetColor", // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
- HideLinkButton = "hideLinkButton", // hides the blue-dot link button. used when a document acts like a button
- LinkSource = "linkSource", // source document of a link -- used by LinkAnchorBox
- PointerEvents = "pointerEvents", // pointer events for DocumentView -- inherits pointer events if not specified
- Decorations = "decorations", // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
- HeaderMargin = "headerMargin", // margin at top of documentview, typically for displaying a title -- doc contents will start below that
- TitleHeight = "titleHeight", // Height of Title area
- ShowTitle = "showTitle", // whether to display a title on a Document (optional :hover suffix)
- JitterRotation = "jitterRotation", // whether documents should be randomly rotated
- BorderPath = "customBorder", // border path for document view
- FontSize = "fontSize", // size of text font
- FontFamily = "fontFamily", // size of text font
+ TreeViewIcon = 'treeViewIcon',
+ TreeViewSortings = 'treeViewSortings', // options for how to sort tree view items
+ DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
+ Opacity = 'opacity', // opacity of the document view
+ Hidden = 'hidden', // whether the document view should not be isplayed
+ BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
+ BorderRounding = 'borderRounding', // border radius of the document view
+ Color = 'color', // foreground color of Document view items
+ BackgroundColor = 'backgroundColor', // background color of a document view
+ WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
+ HideLinkButton = 'hideLinkButton', // hides the blue-dot link button. used when a document acts like a button
+ LinkSource = 'linkSource', // source document of a link -- used by LinkAnchorBox
+ PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
+ Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
+ HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
+ TitleHeight = 'titleHeight', // Height of Title area
+ ShowTitle = 'showTitle', // whether to display a title on a Document (optional :hover suffix)
+ JitterRotation = 'jitterRotation', // whether documents should be randomly rotated
+ BorderPath = 'customBorder', // border path for document view
+ FontSize = 'fontSize', // size of text font
+ FontFamily = 'fontFamily', // size of text font
}
-function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; }
+function darkScheme() {
+ return Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark;
+}
-function toggleBackground(doc: Doc) {
- UndoManager.RunInBatch(() => runInAction(() => {
- const layers = StrListCast(doc._layerTags);
- if (!layers.includes(StyleLayers.Background)) {
- if (!layers.length) doc._layerTags = new List<string>([StyleLayers.Background]);
- else layers.push(StyleLayers.Background);
- }
- else layers.splice(layers.indexOf(StyleLayers.Background), 1);
- }), "toggleBackground");
+function toggleLockedPosition(doc: Doc) {
+ UndoManager.RunInBatch(
+ () =>
+ runInAction(() => {
+ doc._lockedPosition = !doc._lockedPosition;
+ doc._pointerEvents = doc._lockedPosition ? 'none' : undefined;
+ }),
+ 'toggleBackground'
+ );
}
export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps {
- return (toBeDetermined?.isContentActive) ? toBeDetermined : undefined;
+ return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
}
export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
- return `M ${pw * .5} ${ph * inset} C ${pw * .6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * .25} ${pw * (1 - inset)} ${ph * .3} C ${pw * (1 - inset)} ${ph * .4} ${pw} ${ph * (1 - 2 * inset)} ${pw * (1 - inset)} ${ph * (1 - inset)} C ${pw * (1 - 2 * inset)} ${ph} ${pw * .6} ${ph * (1 - inset)} ${pw * .5} ${ph * (1 - inset)} C ${pw * .3} ${ph * (1 - inset)} ${pw * (2 * inset)} ${ph} ${pw * inset} ${ph * (1 - inset)} C 0 ${ph * (1 - 2 * inset)} ${pw * inset} ${ph * .8} ${pw * inset} ${ph * .75} C ${pw * inset} ${ph * .7} 0 ${ph * (2 * inset)} ${pw * inset} ${ph * inset} C ${pw * (2 * inset)} 0 ${pw * .25} ${ph * inset} ${pw * .5} ${ph * inset}`;
+ return `M ${pw * 0.5} ${ph * inset} C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3} C ${
+ pw * (1 - inset)
+ } ${ph * 0.4} ${pw} ${ph * (1 - 2 * inset)} ${pw * (1 - inset)} ${ph * (1 - inset)} C ${pw * (1 - 2 * inset)} ${ph} ${pw * 0.6} ${ph * (1 - inset)} ${pw * 0.5} ${ph * (1 - inset)} C ${pw * 0.3} ${ph * (1 - inset)} ${pw * (2 * inset)} ${ph} ${
+ pw * inset
+ } ${ph * (1 - inset)} C 0 ${ph * (1 - 2 * inset)} ${pw * inset} ${ph * 0.8} ${pw * inset} ${ph * 0.75} C ${pw * inset} ${ph * 0.7} 0 ${ph * (2 * inset)} ${pw * inset} ${ph * inset} C ${pw * (2 * inset)} 0 ${pw * 0.25} ${ph * inset} ${
+ pw * 0.5
+ } ${ph * inset}`;
}
// a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
-//
+//
export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any {
+ const remoteDocHeader = 'author;creationDate;noMargin';
const docProps = testDocProps(props) ? props : undefined;
- const selected = property.includes(":selected");
- const isCaption = property.includes(":caption");
- const isAnchor = property.includes(":anchor");
- const isAnnotated = property.includes(":annotated");
- const isOpen = property.includes(":open");
- const fieldKey = (props as any)?.fieldKey ? (props as any).fieldKey + "-" : isCaption ? "caption-" : "";
- const comicStyle = () => doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === "comic";
- const isBackground = () => StrListCast(doc?._layerTags).includes(StyleLayers.Background);
+ const selected = property.includes(':selected');
+ const isCaption = property.includes(':caption');
+ const isAnchor = property.includes(':anchor');
+ const isAnnotated = property.includes(':annotated');
+ const isOpen = property.includes(':open');
+ const fieldKey = props?.fieldKey ? props.fieldKey + '-' : isCaption ? 'caption-' : '';
+ const comicStyle = () => doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
+ const isBackground = () => doc && BoolCast(doc._lockedPosition);
const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
const showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
- const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + ((Math.abs(x * y) * 9301 + 49297) % 233280 / 233280) * (max - min);
- switch (property.split(":")[0]) {
- case StyleProp.TreeViewIcon: return Doc.toIcon(doc, isOpen);
- case StyleProp.DocContents: return undefined;
- case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey";
+ const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
+ switch (property.split(':')[0]) {
+ case StyleProp.TreeViewIcon:
+ const img = ImageCast(doc?.icon, ImageCast(doc?.data));
+ if (img) {
+ const ext = extname(img.url.href);
+ const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
+ return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
+ }
+ return Doc.toIcon(doc, isOpen);
+
+ case StyleProp.TreeViewSortings:
+ const allSorts: { [key: string]: { color: string; label: string } | undefined } = {};
+ allSorts[TreeSort.Down] = { color: 'blue', label: '↓' };
+ allSorts[TreeSort.Up] = { color: 'crimson', label: '↑' };
+ if (doc?._viewType === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', label: 'z' };
+ allSorts[TreeSort.None] = { color: 'darkgray', label: '\u00A0\u00A0\u00A0' };
+ return allSorts;
+ case StyleProp.DocContents:
+ return undefined;
+ case StyleProp.WidgetColor:
+ return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? 'lightgrey' : 'dimgrey';
// return doc = dragManager.dragDocument ? props.dragEffects.opacity??CastofOpacityonline94 : Cast())
// same idea for background Color
- case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
- case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
- case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
- case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + "fontFamily"], StrCast(doc?.fontFamily, StrCast(Doc.UserDoc().fontFamily)));
- case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
- StrCast(doc._showTitle,
- props?.showTitle?.() ||
- (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ?
- (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) :
- "author;creationDate") : "")) || "");
+ case StyleProp.Opacity:
+ return Cast(doc?._opacity, 'number', Cast(doc?.opacity, 'number', null));
+ case StyleProp.HideLinkButton:
+ return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
+ case StyleProp.FontSize:
+ return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
+ case StyleProp.FontFamily:
+ return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?.fontFamily, StrCast(Doc.UserDoc().fontFamily)));
+ case StyleProp.ShowTitle:
+ return (
+ (doc &&
+ !doc.presentationTargetDoc &&
+ props?.showTitle?.() !== '' &&
+ StrCast(
+ doc._showTitle,
+ props?.showTitle?.() ||
+ (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any)
+ ? doc.author === Doc.CurrentUserEmail
+ ? StrCast(Doc.UserDoc().showTitle)
+ : remoteDocHeader
+ : '')
+ )) ||
+ ''
+ );
case StyleProp.Color:
if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
- const docColor: Opt<string> = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color));
+ const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
if (docColor) return docColor;
- const backColor = backgroundCol();
+ const docView = props?.DocumentView?.();
+ const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, 'backgroundColor');
if (!backColor) return undefined;
return lightOrDark(backColor);
- case StyleProp.Hidden: return BoolCast(doc?._hidden);
- case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + "borderRounding"], doc?._viewType === CollectionViewType.Pile ? "50%" : "");
- case StyleProp.TitleHeight: return 15;
- case StyleProp.BorderPath: return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 };
- case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
- case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
- doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
+ case StyleProp.Hidden:
+ return BoolCast(doc?.hidden);
+ case StyleProp.BorderRounding:
+ return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.borderRounding, doc?._viewType === CollectionViewType.Pile ? '50%' : ''));
+ case StyleProp.TitleHeight:
+ return 15;
+ case StyleProp.BorderPath:
+ return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK
+ ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 }
+ : { path: undefined, width: 0 };
+ case StyleProp.JitterRotation:
+ return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
+ case StyleProp.HeaderMargin:
+ return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
+ (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) ||
+ doc?.type === DocumentType.LABEL) &&
+ showTitle() &&
+ !StrCast(doc?.showTitle).includes(':hover')
+ ? 15
+ : 0;
case StyleProp.BackgroundColor: {
if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY;
- let docColor: Opt<string> = StrCast(doc?.[fieldKey + "backgroundColor"], StrCast(doc?._backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : ""));
+ let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?._backgroundColor, StrCast(props?.Document.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : '')));
switch (doc?.type) {
- case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break;
- case DocumentType.PRES: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
- case DocumentType.FONTICON: docColor = docColor || Colors.DARK_GRAY; break;
- case DocumentType.RTF: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
- case DocumentType.FILTER: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : "rgba(105, 105, 105, 0.432)"); break;
- case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break;
- case DocumentType.SLIDER: break;
- case DocumentType.EQUATION: docColor = docColor || "transparent"; break;
- case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
- case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
- case DocumentType.LINKANCHOR: docColor = isAnchor ? Colors.LIGHT_BLUE : "transparent"; break;
- case DocumentType.LINK: docColor = (isAnchor ? docColor : "") || "transparent"; break;
+ case DocumentType.PRESELEMENT:
+ docColor = docColor || (darkScheme() ? '' : '');
+ break;
+ case DocumentType.PRES:
+ docColor = docColor || (darkScheme() ? 'transparent' : 'transparent');
+ break;
+ case DocumentType.FONTICON:
+ docColor = docColor || Colors.DARK_GRAY;
+ break;
+ case DocumentType.RTF:
+ docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY);
+ break;
+ case DocumentType.FILTER:
+ docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : 'rgba(105, 105, 105, 0.432)');
+ break;
+ case DocumentType.INK:
+ docColor = doc?.isInkMask ? 'rgba(0,0,0,0.7)' : undefined;
+ break;
+ case DocumentType.SLIDER:
+ break;
+ case DocumentType.EQUATION:
+ docColor = docColor || 'transparent';
+ break;
+ case DocumentType.LABEL:
+ docColor = docColor || (doc.annotationOn !== undefined ? 'rgba(128, 128, 128, 0.18)' : undefined) || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY);
+ break;
+ case DocumentType.BUTTON:
+ docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY);
+ break;
+ case DocumentType.LINKANCHOR:
+ docColor = isAnchor ? Colors.LIGHT_BLUE : 'transparent';
+ break;
+ case DocumentType.LINK:
+ docColor = (isAnchor ? docColor : '') || 'transparent';
+ break;
case DocumentType.IMG:
case DocumentType.WEB:
case DocumentType.PDF:
case DocumentType.MAP:
case DocumentType.SCREENSHOT:
- case DocumentType.VID: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
+ case DocumentType.VID:
+ docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY);
+ break;
case DocumentType.COL:
if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
- docColor = docColor ||
- (doc?._isGroup ? "#00000004" : // very faint highlight to show bounds of group
- (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background
- isBackground() ? "cyan" : // ?? is there a good default for a background collection
- doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
- StrCast((props?.renderDepth || 0) > 0 ?
- Doc.UserDoc().activeCollectionNestedBackground :
- Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE))));
+ docColor =
+ docColor ||
+ (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc)
+ ? darkScheme()
+ ? Colors.DARK_GRAY
+ : Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background
+ : doc.annotationOn
+ ? '#00000015' // faint interior for collections on PDFs, images, etc
+ : doc?._isGroup
+ ? undefined
+ : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : 'linear-gradient(#065fff, #85c1f9)'));
break;
//if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
- default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
+ default:
+ docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE);
+ break;
}
- if (docColor && (!doc || props?.layerProvider?.(doc) === false)) docColor = DashColor(docColor).fade(0.5).toString();
+ if (docColor && !doc) docColor = DashColor(docColor).fade(0.5).toString();
return docColor;
}
case StyleProp.BoxShadow: {
- if (!doc || opacity() === 0) return undefined; // if it's not visible, then no shadow)
+ if (!doc || opacity() === 0) return undefined; // if it's not visible, then no shadow)
- if (doc?.isLinkButton && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, "lightblue 0em 0em 1em");
+ if (doc?.isLinkButton && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
switch (doc?.type) {
case DocumentType.COL:
- return StrCast(doc?.boxShadow,
- doc?._viewType === CollectionViewType.Pile ? "4px 4px 10px 2px" :
- isBackground() || doc?._isGroup || docProps?.LayoutTemplateString ? undefined : // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
- `${darkScheme() ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`);
+ return StrCast(
+ doc?.boxShadow,
+ doc?._viewType === CollectionViewType.Pile
+ ? '4px 4px 10px 2px'
+ : isBackground() || doc?._isGroup || docProps?.LayoutTemplateString
+ ? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
+ : `${darkScheme() ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY} ${StrCast(doc.boxShadow, '0.2vw 0.2vw 0.8vw')}`
+ );
case DocumentType.LABEL:
- if (doc?.annotationOn !== undefined) return "black 2px 2px 1px";
+ if (doc?.annotationOn !== undefined) return 'black 2px 2px 1px';
default:
- return doc.z ? `#9c9396 ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow
- props?.ContainingCollectionDoc?._useClusters && doc.type !== DocumentType.INK ? (`${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.ContentScaling?.() || 1)}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
- NumCast(doc.group, -1) !== -1 && doc.type !== DocumentType.INK ? (`gray ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.ContentScaling?.() || 1)}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
- isBackground() ? undefined : // if it's a background & has a cluster color, make the shadow spread really big
- StrCast(doc.boxShadow, "");
+ return doc.z
+ ? `#9c9396 ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
+ : props?.ContainingCollectionDoc?._useClusters && doc.type !== DocumentType.INK
+ ? `${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ : NumCast(doc.group, -1) !== -1 && doc.type !== DocumentType.INK
+ ? `gray ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ : isBackground()
+ ? undefined // if it's a background & has a cluster color, make the shadow spread really big
+ : StrCast(doc.boxShadow, '');
}
}
case StyleProp.PointerEvents:
- if (doc?.type === DocumentType.MARKER) return "none";
- if (props?.pointerEvents === "none") return "none";
- const layer = doc && props?.layerProvider?.(doc);
- if (opacity() === 0 || (doc?.type === DocumentType.INK && !docProps?.treeViewDoc) || doc?.isInkMask) return "none";
- if (layer === false && !selected && !SnappingManager.GetIsDragging()) return "none";
- if (doc?.type !== DocumentType.INK && layer === true) return "all";
+ if (doc?.pointerEvents) return StrCast(doc.pointerEvents);
+ if (props?.pointerEvents?.() === 'none') return 'none';
+ const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name);
+ if (opacity() === 0 || (isInk && !docProps?.treeViewDoc) || doc?.isInkMask) return 'none';
+ if (!isInk) return 'all';
return undefined;
case StyleProp.Decorations:
- if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) {
- return doc && (isBackground() || selected) && (props?.renderDepth || 0) > 0 &&
- ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ?
- <div className="styleProvider-lock" onClick={() => toggleBackground(doc)}>
- <FontAwesomeIcon icon={isBackground() ? "lock" : "unlock"} style={{ color: isBackground() ? "red" : undefined }} size="lg" />
+ if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform || doc?.x !== undefined || doc?.y !== undefined) {
+ return doc &&
+ (isBackground() || selected) &&
+ !Doc.IsSystem(doc) &&
+ (props?.renderDepth || 0) > 0 &&
+ ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? (
+ <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
+ <FontAwesomeIcon icon={isBackground() ? 'lock' : 'unlock'} style={{ color: isBackground() ? 'red' : undefined }} size="lg" />
</div>
- : (null);
+ ) : null;
}
}
}
export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, offIcon: IconProp, clickFunc?: () => void) {
- return <div className={`styleProvider-treeView-icon${doc[field] ? "-active" : ""}`}
- onClick={undoBatch(action((e: React.MouseEvent) => {
- e.stopPropagation();
- clickFunc ? clickFunc() : (doc[field] = doc[field] ? undefined : true);
- }))}>
- <FontAwesomeIcon icon={(doc[field] ? onIcon as any : offIcon) as IconProp} size="sm" />
- </div>;
+ return (
+ <div
+ title={field}
+ className={`styleProvider-treeView-icon${doc[field] ? '-active' : ''}`}
+ onClick={undoBatch(
+ action((e: React.MouseEvent) => {
+ e.stopPropagation();
+ clickFunc ? clickFunc() : (doc[field] = doc[field] ? undefined : true);
+ })
+ )}>
+ <FontAwesomeIcon icon={(doc[field] ? (onIcon as any) : offIcon) as IconProp} size="sm" />
+ </div>
+ );
}
/**
* add lock and hide button decorations for the "Dashboards" flyout TreeView
*/
export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
-
- if (doc && property.split(":")[0] === StyleProp.Decorations) {
- return doc._viewType === CollectionViewType.Docking ? (null) :
+ if (doc && property.split(':')[0] === StyleProp.Decorations) {
+ return doc._viewType === CollectionViewType.Docking ? null : (
<>
- {DashboardToggleButton(doc, "hidden", "eye-slash", "eye")}
- {DashboardToggleButton(doc, "lockedPosition", "lock", "unlock")}
- </>;
+ {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => {
+ doc.hidden = doc.hidden ? undefined : true;
+ if (!doc.hidden) {
+ DocFocusOrOpen(doc, props?.ContainingCollectionDoc);
+ }
+ })}
+ </>
+ );
}
return DefaultStyleProvider(doc, props, property);
}
-
-//
-// a preliminary semantic-"layering/grouping" mechanism for determining interactive properties of documents
-// currently, the provider tests whether the docuemnt's layer field matches the activeLayer field of the tab.
-// if it matches, then the document gets pointer events, otherwise it does not.
-//
-export function DefaultLayerProvider(thisDoc: Doc) {
- return (doc: Doc, assign?: boolean) => {
- if (doc.z) return true;
- if (assign) {
- const activeLayer = StrCast(thisDoc?.activeLayer);
- if (activeLayer) {
- const layers = Cast(doc._layerTags, listSpec("string"), []);
- if (layers.length && !layers.includes(activeLayer)) layers.push(activeLayer);
- else if (!layers.length) doc._layerTags = new List<string>([activeLayer]);
- if (activeLayer === "red" || activeLayer === "green" || activeLayer === "blue") doc._backgroundColor = activeLayer;
- }
- return true;
- } else {
- if (Doc.AreProtosEqual(doc, thisDoc)) return true;
- const layers = StrListCast(doc._layerTags);
- if (!layers.length && !thisDoc?.activeLayer) return true;
- if (layers.includes(StrCast(thisDoc?.activeLayer))) return true;
- return false;
- }
- };
-} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index b3a24e031..863829a51 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,42 +1,42 @@
-import { action, computed, observable, ObservableSet, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { ScriptField } from "../../fields/ScriptField";
-import { Cast, StrCast } from "../../fields/Types";
-import { TraceMobx } from "../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils";
-import { Docs, DocUtils } from "../documents/Documents";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { Transform } from "../util/Transform";
-import { undoBatch } from "../util/UndoManager";
-import { CollectionTreeView } from "./collections/CollectionTreeView";
-import { DocumentView } from "./nodes/DocumentView";
-import { DefaultStyleProvider } from "./StyleProvider";
+import { action, computed, observable, ObservableSet, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { ScriptField } from '../../fields/ScriptField';
+import { Cast, StrCast } from '../../fields/Types';
+import { TraceMobx } from '../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
+import { Docs, DocUtils } from '../documents/Documents';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { Transform } from '../util/Transform';
+import { undoBatch } from '../util/UndoManager';
+import { CollectionTreeView } from './collections/CollectionTreeView';
+import { DocumentView } from './nodes/DocumentView';
+import { DefaultStyleProvider } from './StyleProvider';
import './TemplateMenu.scss';
-import React = require("react");
+import React = require('react');
@observer
-class TemplateToggle extends React.Component<{ template: string, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
+class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
render() {
if (this.props.template) {
return (
<li className="templateToggle">
- <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event, this.props.template)} />
+ <input type="checkbox" checked={this.props.checked} onChange={event => this.props.toggle(event, this.props.template)} />
{this.props.template}
</li>
);
} else {
- return (null);
+ return null;
}
}
}
@observer
-class OtherToggle extends React.Component<{ checked: boolean, name: string, toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> {
+class OtherToggle extends React.Component<{ checked: boolean; name: string; toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> {
render() {
return (
<li className="chromeToggle">
- <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event)} />
+ <input type="checkbox" checked={this.props.checked} onChange={event => this.props.toggle(event)} />
{this.props.name}
</li>
);
@@ -48,7 +48,6 @@ export interface TemplateMenuProps {
templates?: Map<string, boolean>;
}
-
@observer
export class TemplateMenu extends React.Component<TemplateMenuProps> {
_addedKeys = new ObservableSet();
@@ -56,111 +55,101 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@observable private _hidden: boolean = true;
toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
- this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout));
- }
+ this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout, undefined, true));
+ };
toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => {
- this.props.docViews.map(dv => dv.switchViews(false, "layout"));
- }
-
- toggleAudio = (e: React.ChangeEvent<HTMLInputElement>): void => {
- this.props.docViews.map(dv => dv.props.Document._showAudio = e.target.checked);
- }
+ this.props.docViews.map(dv => dv.switchViews(false, 'layout'));
+ };
@undoBatch
@action
toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: string): void => {
- this.props.docViews.forEach(d => Doc.Layout(d.layoutDoc)["_show" + template] = event.target.checked ? template.toLowerCase() : "");
- }
+ this.props.docViews.forEach(d => (Doc.Layout(d.layoutDoc)['_show' + template] = event.target.checked ? template.toLowerCase() : ''));
+ };
@action
toggleTemplateActivity = (): void => {
this._hidden = !this._hidden;
- }
-
- @undoBatch
- @action
- toggleChrome = (): void => {
- this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout => layout._chromeHidden = !layout._chromeHidden);
- }
+ };
// todo: add brushes to brushMap to save with a style name
onCustomKeypress = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
runInAction(() => this._addedKeys.add(this._customRef.current!.value));
}
- }
+ };
componentDidMount() {
!this._addedKeys && (this._addedKeys = new ObservableSet());
- Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))).
- filter(key => key.startsWith("layout_")).
- map(key => runInAction(() => this._addedKeys.add(key.replace("layout_", ""))));
+ Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document)))
+ .filter(key => key.startsWith('layout_'))
+ .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
}
- return100 = () => 100;
+ return100 = () => 300;
@computed get scriptField() {
- const script = ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
- { docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) });
+ const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.props.Document) as any });
return script ? () => script : undefined;
}
templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
- return StrCast(selDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
- }
+ return StrCast(selDoc.layoutKey) === 'layout_' + template ? 'check' : 'unchecked';
+ };
render() {
TraceMobx();
const firstDoc = this.props.docViews[0].props.Document;
- const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
- const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
- const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
- const layout = Doc.Layout(firstDoc);
+ const templateName = StrCast(firstDoc.layoutKey, 'layout').replace('layout_', '');
+ const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data);
+ const addedTypes = DocListCast(Cast(Doc.UserDoc()['template-clickFuncs'], Doc, null)?.data);
const templateMenu: Array<JSX.Element> = [];
- this.props.templates?.forEach((checked, template) =>
- templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
- templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
- templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
- !Doc.UserDoc().noviceMode && templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={!layout._chromeHidden} toggle={this.toggleChrome} />);
- addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template));
- this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
- <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
- return <ul className="template-list" style={{ display: "block" }}>
- {Doc.UserDoc().noviceMode ? (null) : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
- {templateMenu}
- {Doc.UserDoc().noviceMode ? (null) : <CollectionTreeView
- Document={Doc.UserDoc().templateDocs as Doc}
- CollectionView={undefined}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- setHeight={returnFalse}
- docViewPath={returnEmptyDoclist}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- rootSelected={returnFalse}
- onCheckedClick={this.scriptField}
- onChildClick={this.scriptField}
- dropAction={undefined}
- isAnyChildContentActive={returnFalse}
- isContentActive={returnTrue}
- bringToFront={emptyFunction}
- focus={emptyFunction}
- whenChildContentsActiveChanged={emptyFunction}
- ScreenToLocalTransform={Transform.Identity}
- isSelected={returnFalse}
- pinToPres={emptyFunction}
- select={emptyFunction}
- renderDepth={1}
- addDocTab={returnFalse}
- PanelWidth={this.return100}
- PanelHeight={this.return100}
- treeViewHideHeaderFields={true}
- dontRegisterView={true}
- fieldKey={"data"}
- moveDocument={returnFalse}
- removeDocument={returnFalse}
- addDocument={returnFalse} />}
- </ul>;
+ this.props.templates?.forEach((checked, template) => templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
+ templateMenu.push(<OtherToggle key={'default'} name={'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
+ addedTypes.concat(noteTypes).map(template => (template.treeViewChecked = this.templateIsUsed(firstDoc, template)));
+ this._addedKeys &&
+ Array.from(this._addedKeys)
+ .filter(key => !noteTypes.some(nt => nt.title === key))
+ .forEach(template => templateMenu.push(<OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
+ return (
+ <ul className="template-list" style={{ display: 'block' }}>
+ {Doc.noviceMode ? null : <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />}
+ {templateMenu}
+ <CollectionTreeView
+ Document={Doc.MyTemplates}
+ CollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ styleProvider={DefaultStyleProvider}
+ setHeight={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ rootSelected={returnFalse}
+ onCheckedClick={this.scriptField}
+ onChildClick={this.scriptField}
+ dropAction={undefined}
+ isAnyChildContentActive={returnFalse}
+ isContentActive={returnTrue}
+ bringToFront={emptyFunction}
+ focus={emptyFunction}
+ whenChildContentsActiveChanged={emptyFunction}
+ ScreenToLocalTransform={Transform.Identity}
+ isSelected={returnFalse}
+ pinToPres={emptyFunction}
+ select={emptyFunction}
+ renderDepth={1}
+ addDocTab={returnFalse}
+ PanelWidth={this.return100}
+ PanelHeight={this.return100}
+ treeViewHideHeaderFields={true}
+ treeViewHideTitle={true}
+ dontRegisterView={true}
+ fieldKey={'data'}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ addDocument={returnFalse}
+ />
+ </ul>
+ );
}
}
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 789756a78..436cb688f 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -4,7 +4,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
const HOLD_DURATION = 1000;
-export abstract class Touchable<T = {}> extends React.Component<T> {
+export abstract class Touchable<T = {}> extends React.Component<React.PropsWithChildren<T>> {
//private holdTimer: NodeJS.Timeout | undefined;
private moveDisposer?: InteractionUtils.MultiTouchEventDisposer;
private endDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -25,7 +25,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
*/
@action
protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
-
const actualPts: React.Touch[] = [];
const te = me.touchEvent;
// loop through all touches on screen
@@ -60,7 +59,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
case 1:
this.handle1PointerDown(te, me);
te.persist();
- // -- code for radial menu --
+ // -- code for radial menu --
// if (this.holdTimer) {
// clearTimeout(this.holdTimer)
// this.holdTimer = undefined;
@@ -71,11 +70,11 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
break;
}
}
- }
+ };
/**
- * Handle touch move event
- */
+ * Handle touch move event
+ */
@action
protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
const te = me.touchEvent;
@@ -85,8 +84,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
this._touchDrag = true;
switch (myTouches.length) {
- case 1: this.handle1PointerMove(te, me); break;
- case 2: this.handle2PointersMove(te, me); break;
+ case 1:
+ this.handle1PointerMove(te, me);
+ break;
+ case 2:
+ this.handle2PointersMove(te, me);
+ break;
}
for (const pt of me.touches) {
@@ -94,7 +97,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
this.prevPoints.set(pt.identifier, pt);
}
}
- }
+ };
@action
protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
@@ -110,7 +113,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
this._touchDrag = false;
te.stopPropagation();
-
// if (e.targetTouches.length === 0) {
// this.prevPoints.clear();
// }
@@ -119,36 +121,36 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
this.cleanUpInteractions();
}
e.stopPropagation();
- }
+ };
cleanUpInteractions = (): void => {
this.removeMoveListeners();
this.removeEndListeners();
- }
+ };
handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
e.stopPropagation();
e.preventDefault();
- }
+ };
handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
e.stopPropagation();
e.preventDefault();
- }
+ };
handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.addMoveListeners();
this.removeEndListeners();
this.addEndListeners();
- }
+ };
handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.addMoveListeners();
this.removeEndListeners();
this.addEndListeners();
- }
+ };
handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
e.stopPropagation();
@@ -159,30 +161,30 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
this.removeHoldEndListeners();
this.addHoldMoveListeners();
this.addHoldEndListeners();
- }
+ };
addMoveListeners = () => {
const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener("dashOnTouchMove", handler);
- this.moveDisposer = () => document.removeEventListener("dashOnTouchMove", handler);
- }
+ document.addEventListener('dashOnTouchMove', handler);
+ this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler);
+ };
addEndListeners = () => {
const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener("dashOnTouchEnd", handler);
- this.endDisposer = () => document.removeEventListener("dashOnTouchEnd", handler);
- }
+ document.addEventListener('dashOnTouchEnd', handler);
+ this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler);
+ };
addHoldMoveListeners = () => {
const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener("dashOnTouchHoldMove", handler);
- this.holdMoveDisposer = () => document.removeEventListener("dashOnTouchHoldMove", handler);
- }
+ document.addEventListener('dashOnTouchHoldMove', handler);
+ this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler);
+ };
addHoldEndListeners = () => {
const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
- document.addEventListener("dashOnTouchHoldEnd", handler);
- this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler);
- }
+ document.addEventListener('dashOnTouchHoldEnd', handler);
+ this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler);
+ };
removeMoveListeners = () => this.moveDisposer?.();
removeEndListeners = () => this.endDisposer?.();
@@ -192,7 +194,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
// e.stopPropagation();
// me.touchEvent.stopPropagation();
- }
+ };
handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
e.stopPropagation();
@@ -202,10 +204,10 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
me.touchEvent.stopPropagation();
me.touchEvent.preventDefault();
- }
+ };
handleHandDown = (e: React.TouchEvent) => {
// e.stopPropagation();
// e.preventDefault();
- }
-} \ No newline at end of file
+ };
+}
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index fd0ac9d5c..17eff022f 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -1,4 +1,4 @@
-@import "./global/globalCssVariables";
+@import './global/globalCssVariables';
// this file is for overriding all the css from installed node modules
// goldenlayout stuff
@@ -56,16 +56,5 @@ div .lm_header {
font-family: $sans-serif !important;
}
-.lm_header .lm_controls {
- align-items: center;
- position: absolute;
- background-color: $dark-gray;
- border-radius: 5px;
- display: flex;
- justify-content: space-evenly;
- height: 23px;
- width: 65px;
-}
-
// @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out
-// why. Low priority for now but it's bugging me. --Julie \ No newline at end of file
+// why. Low priority for now but it's bugging me. --Julie
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index e80ba6f36..7a393a4f7 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -1,19 +1,19 @@
-import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from "@fortawesome/free-solid-svg-icons";
+import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc, DocListCast } from "../../../fields/Doc";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
-import { FieldViewProps } from "../nodes/FieldView";
-import { KeyframeFunc } from "./Keyframe";
-import "./Timeline.scss";
-import { TimelineOverview } from "./TimelineOverview";
-import { Track } from "./Track";
-import clamp from "../../util/clamp";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { IconLookup } from "@fortawesome/fontawesome-svg-core";
+import { action, computed, observable, runInAction, trace } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from '../../../Utils';
+import { FieldViewProps } from '../nodes/FieldView';
+import { KeyframeFunc } from './Keyframe';
+import './Timeline.scss';
+import { TimelineOverview } from './TimelineOverview';
+import { Track } from './Track';
+import clamp from '../../util/clamp';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { IconLookup } from '@fortawesome/fontawesome-svg-core';
/**
* Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are
@@ -35,10 +35,8 @@ import { IconLookup } from "@fortawesome/fontawesome-svg-core";
@author Andrew Kim
*/
-
@observer
export class Timeline extends React.Component<FieldViewProps> {
-
//readonly constants
private readonly DEFAULT_TICK_SPACING: number = 50;
private readonly MAX_TITLE_HEIGHT = 75;
@@ -57,8 +55,7 @@ export class Timeline extends React.Component<FieldViewProps> {
@observable private _roundToggleRef = React.createRef<HTMLDivElement>();
@observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>();
-
- //boolean vars and instance vars
+ //boolean vars and instance vars
@observable private _currentBarX: number = 0;
@observable private _windSpeed: number = 1;
@observable private _isPlaying: boolean = false; //scrubber playing
@@ -73,13 +70,13 @@ export class Timeline extends React.Component<FieldViewProps> {
@observable private _titleHeight = 0;
/**
- * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit.
+ * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit.
*/
@computed
private get children(): Doc[] {
const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this.props.Document.type) as any);
if (annotatedDoc) {
- return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"]);
+ return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + '-annotations']);
}
return DocListCast(this.props.Document[this.props.fieldKey]);
}
@@ -91,7 +88,8 @@ export class Timeline extends React.Component<FieldViewProps> {
this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max
this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset
this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset
- if (!this.props.Document.AnimationLength) { //if animation length did not exist
+ if (!this.props.Document.AnimationLength) {
+ //if animation length did not exist
this.props.Document.AnimationLength = this._time; //set it to default time
} else {
this._time = NumCast(this.props.Document.AnimationLength); //else, set time to animationlength stored from before
@@ -116,24 +114,29 @@ export class Timeline extends React.Component<FieldViewProps> {
drawTicks = () => {
const ticks = [];
for (let i = 0; i < this._time / this._tickIncrement; i++) {
- ticks.push(<div key={Utils.GenerateGuid()} className="tick" style={{ transform: `translate(${i * this._tickSpacing}px)`, position: "absolute", pointerEvents: "none" }}> <p className="number-label">{this.toReadTime(i * this._tickIncrement)}</p></div>);
+ ticks.push(
+ <div key={Utils.GenerateGuid()} className="tick" style={{ transform: `translate(${i * this._tickSpacing}px)`, position: 'absolute', pointerEvents: 'none' }}>
+ {' '}
+ <p className="number-label">{this.toReadTime(i * this._tickIncrement)}</p>
+ </div>
+ );
}
return ticks;
- }
+ };
/**
* changes the scrubber to actual pixel position
*/
@action
changeCurrentBarX = (pixel: number) => {
- pixel <= 0 ? this._currentBarX = 0 : pixel >= this._totalLength ? this._currentBarX = this._totalLength : this._currentBarX = pixel;
- }
+ pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel);
+ };
//for playing
onPlay = (e: React.MouseEvent) => {
e.stopPropagation();
this.play();
- }
+ };
/**
* when playbutton is clicked
@@ -149,8 +152,7 @@ export class Timeline extends React.Component<FieldViewProps> {
this._isPlaying = !this._isPlaying;
this._playButton = this._isPlaying ? faPauseCircle : faPlayCircle;
this._isPlaying && playTimeline();
- }
-
+ };
/**
* fast forward the timeline scrubbing
@@ -159,30 +161,32 @@ export class Timeline extends React.Component<FieldViewProps> {
windForward = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
- if (this._windSpeed < 64) { //max speed is 32
+ if (this._windSpeed < 64) {
+ //max speed is 32
this._windSpeed = this._windSpeed * 2;
}
- }
+ };
/**
- * rewind the timeline scrubbing
+ * rewind the timeline scrubbing
*/
@action
windBackward = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
- if (this._windSpeed > 1 / 16) { // min speed is 1/8
+ if (this._windSpeed > 1 / 16) {
+ // min speed is 1/8
this._windSpeed = this._windSpeed / 2;
}
- }
+ };
/**
- * scrubber down
+ * scrubber down
*/
@action
onScrubberDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.onScrubberMove, emptyFunction, emptyFunction);
- }
+ };
/**
* when there is any scrubber movement
@@ -194,16 +198,15 @@ export class Timeline extends React.Component<FieldViewProps> {
const offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale;
this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
return false;
- }
+ };
/**
* when panning the timeline (in editing mode)
*/
@action
onPanDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, (e) =>
- this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left));
- }
+ setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, e => this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left));
+ };
/**
* when moving the timeline (in editing mode)
@@ -218,29 +221,34 @@ export class Timeline extends React.Component<FieldViewProps> {
if (this._visibleStart + this._visibleLength + 20 >= this._totalLength) {
this._visibleStart -= e.movementX;
this._totalLength -= e.movementX;
- this._time -= KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this._tickSpacing, this._tickIncrement);
+ this._time -= KeyframeFunc.convertPixelTime(e.movementX, 'mili', 'time', this._tickSpacing, this._tickIncrement);
this.props.Document.AnimationLength = this._time;
}
return false;
- }
-
+ };
@action
movePanX = (pixel: number) => {
this._infoContainer.current!.scrollLeft = pixel;
this._visibleStart = this._infoContainer.current!.scrollLeft;
- }
+ };
/**
* resizing timeline (in editing mode) (the hamburger drag icon)
*/
onResizeDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, action((e) => {
- const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
- this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT);
- return false;
- }), emptyFunction, emptyFunction);
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => {
+ const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
+ this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT);
+ return false;
+ }),
+ emptyFunction,
+ emptyFunction
+ );
+ };
/**
* for displaying time to standard min:sec
@@ -251,19 +259,18 @@ export class Timeline extends React.Component<FieldViewProps> {
const inSeconds = Math.round(time * 100) / 100;
const min = Math.floor(inSeconds / 60);
- const sec = (Math.round((inSeconds % 60) * 100) / 100);
+ const sec = Math.round((inSeconds % 60) * 100) / 100;
let secString = sec.toFixed(2);
if (Math.floor(sec / 10) === 0) {
- secString = "0" + secString;
+ secString = '0' + secString;
}
return `${min}:${secString}`;
- }
-
+ };
/**
- * timeline zoom function
+ * timeline zoom function
* use mouse middle button to zoom in/out the timeline
*/
@action
@@ -271,17 +278,16 @@ export class Timeline extends React.Component<FieldViewProps> {
e.preventDefault();
e.stopPropagation();
const offset = e.clientX - this._infoContainer.current!.getBoundingClientRect().left;
- const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, "mili", "time", this._tickSpacing, this._tickIncrement);
- const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement);
+ const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, 'mili', 'time', this._tickSpacing, this._tickIncrement);
+ const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, 'mili', 'time', this._tickSpacing, this._tickIncrement);
this.zoom(e.deltaY < 0);
- const currPixel = KeyframeFunc.convertPixelTime(prevTime, "mili", "pixel", this._tickSpacing, this._tickIncrement);
- const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, "mili", "pixel", this._tickSpacing, this._tickIncrement);
+ const currPixel = KeyframeFunc.convertPixelTime(prevTime, 'mili', 'pixel', this._tickSpacing, this._tickIncrement);
+ const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, 'mili', 'pixel', this._tickSpacing, this._tickIncrement);
this._infoContainer.current!.scrollLeft = currPixel - offset;
this._visibleStart = currPixel - offset > 0 ? currPixel - offset : 0;
this._visibleStart += this._visibleLength + this._visibleStart > this._totalLength ? this._totalLength - (this._visibleStart + this._visibleLength) : 0;
this.changeCurrentBarX(currCurrent);
- }
-
+ };
resetView(doc: Doc) {
doc._panX = doc._customOriginX ?? 0;
@@ -324,7 +330,7 @@ export class Timeline extends React.Component<FieldViewProps> {
this._tickSpacing = spacingChange;
this._tickIncrement = incrementChange;
}
- }
+ };
/**
* tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode)
@@ -333,60 +339,90 @@ export class Timeline extends React.Component<FieldViewProps> {
const size = 40 * scale; //50 is default
const iconSize = 25;
const width: number = this.props.PanelWidth();
- const modeType = this.props.Document.isATOn ? "Author" : "Play";
+ const modeType = this.props.Document.isATOn ? 'Author' : 'Play';
//decides if information should be omitted because the timeline is very small
// if its less than 950 pixels then it's going to be overlapping
- let modeString = modeType, overviewString = "", lengthString = "";
+ let modeString = modeType,
+ overviewString = '',
+ lengthString = '';
if (width < 850) {
- modeString = "Mode: " + modeType;
- overviewString = "Overview:";
- lengthString = "Length: ";
+ modeString = 'Mode: ' + modeType;
+ overviewString = 'Overview:';
+ lengthString = 'Length: ';
}
return (
<div key="timeline_toolbox" className="timeline-toolbox" style={{ height: `${size}px` }}>
<div className="playbackControls">
- <div className="timeline-icon" key="timeline_windBack" onClick={this.windBackward} title="Slow Down Animation"> <FontAwesomeIcon icon={faBackward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div>
- <div className="timeline-icon" key=" timeline_play" onClick={this.onPlay} title="Play/Pause"> <FontAwesomeIcon icon={this._playButton as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div>
- <div className="timeline-icon" key="timeline_windForward" onClick={this.windForward} title="Speed Up Animation"> <FontAwesomeIcon icon={faForward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div>
+ <div className="timeline-icon" key="timeline_windBack" onClick={this.windBackward} title="Slow Down Animation">
+ {' '}
+ <FontAwesomeIcon icon={faBackward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} />{' '}
+ </div>
+ <div className="timeline-icon" key=" timeline_play" onClick={this.onPlay} title="Play/Pause">
+ {' '}
+ <FontAwesomeIcon icon={this._playButton as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} />{' '}
+ </div>
+ <div className="timeline-icon" key="timeline_windForward" onClick={this.windForward} title="Speed Up Animation">
+ {' '}
+ <FontAwesomeIcon icon={faForward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} />{' '}
+ </div>
</div>
<div className="grid-box overview-tool">
<div className="overview-box">
- <div key="overview-text" className="animation-text">{overviewString}</div>
- <TimelineOverview tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} time={this._time} parent={this} isAuthoring={BoolCast(this.props.Document.isATOn)} currentBarX={this._currentBarX} totalLength={this._totalLength} visibleLength={this._visibleLength} visibleStart={this._visibleStart} changeCurrentBarX={this.changeCurrentBarX} movePanX={this.movePanX} />
+ <div key="overview-text" className="animation-text">
+ {overviewString}
+ </div>
+ <TimelineOverview
+ tickSpacing={this._tickSpacing}
+ tickIncrement={this._tickIncrement}
+ time={this._time}
+ parent={this}
+ isAuthoring={BoolCast(this.props.Document.isATOn)}
+ currentBarX={this._currentBarX}
+ totalLength={this._totalLength}
+ visibleLength={this._visibleLength}
+ visibleStart={this._visibleStart}
+ changeCurrentBarX={this.changeCurrentBarX}
+ movePanX={this.movePanX}
+ />
</div>
<div className="mode-box overview-tool">
- <div key="animation-text" className="animation-text">{modeString}</div>
+ <div key="animation-text" className="animation-text">
+ {modeString}
+ </div>
<div key="round-toggle" ref={this._roundToggleContainerRef} className="round-toggle">
- <div key="round-toggle-slider" ref={this._roundToggleRef} className="round-toggle-slider" onPointerDown={this.toggleChecked}> </div>
+ <div key="round-toggle-slider" ref={this._roundToggleRef} className="round-toggle-slider" onPointerDown={this.toggleChecked}>
+ {' '}
+ </div>
</div>
</div>
- <div className="time-box overview-tool" style={{ display: "flex" }}>
+ <div className="time-box overview-tool" style={{ display: 'flex' }}>
{this.timeIndicator(lengthString, totalTime)}
- <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}><FontAwesomeIcon icon="compress-arrows-alt" size="lg" /></div>
- <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div>
+ <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}>
+ <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
+ </div>
+ <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this.props.Document)}>
+ <FontAwesomeIcon icon="expand-arrows-alt" size="lg" />
+ </div>
</div>
</div>
</div>
);
- }
+ };
timeIndicator(lengthString: string, totalTime: number) {
if (this.props.Document.isATOn) {
- return (
- <div key="time-text" className="animation-text" style={{ visibility: this.props.Document.isATOn ? "visible" : "hidden", display: this.props.Document.isATOn ? "flex" : "none" }}>{`Total: ${this.toReadTime(totalTime)}`}</div>
- );
- }
- else {
+ return <div key="time-text" className="animation-text" style={{ visibility: this.props.Document.isATOn ? 'visible' : 'hidden', display: this.props.Document.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>;
+ } else {
const ctime = `Current: ${this.getCurrentTime()}`;
const ttime = `Total: ${this.toReadTime(this._time)}`;
return (
- <div style={{ flexDirection: "column" }}>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ <div style={{ flexDirection: 'column' }}>
+ <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this.props.Document.isATOn ? 'block' : 'none' }}>
{ctime}
</div>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this.props.Document.isATOn ? 'block' : 'none' }}>
{ttime}
</div>
</div>
@@ -402,7 +438,7 @@ export class Timeline extends React.Component<FieldViewProps> {
e.preventDefault();
e.stopPropagation();
this.toggleHandle();
- }
+ };
/**
* turns on the toggle button (the purple slide button that changes from editing mode and play mode
@@ -415,23 +451,22 @@ export class Timeline extends React.Component<FieldViewProps> {
this.props.Document.isATOn = !this.props.Document.isATOn;
if (!BoolCast(this.props.Document.isATOn)) {
//turning on playmode...
- roundToggle.style.transform = "translate(0px, 0px)";
- roundToggle.style.animationName = "turnoff";
- roundToggleContainer.style.animationName = "turnoff";
- roundToggleContainer.style.backgroundColor = "white";
+ roundToggle.style.transform = 'translate(0px, 0px)';
+ roundToggle.style.animationName = 'turnoff';
+ roundToggleContainer.style.animationName = 'turnoff';
+ roundToggleContainer.style.backgroundColor = 'white';
timelineContainer.style.top = `${-this._containerHeight}px`;
this.toPlay();
} else {
//turning on authoring mode...
- roundToggle.style.transform = "translate(20px, 0px)";
- roundToggle.style.animationName = "turnon";
- roundToggleContainer.style.animationName = "turnon";
- roundToggleContainer.style.backgroundColor = "#9acedf";
- timelineContainer.style.top = "0px";
+ roundToggle.style.transform = 'translate(20px, 0px)';
+ roundToggle.style.animationName = 'turnon';
+ roundToggleContainer.style.animationName = 'turnon';
+ roundToggleContainer.style.backgroundColor = '#9acedf';
+ timelineContainer.style.top = '0px';
this.toAuthoring();
}
- }
-
+ };
@action.bound
changeLengths() {
@@ -443,9 +478,9 @@ export class Timeline extends React.Component<FieldViewProps> {
// @computed
getCurrentTime = () => {
- const current = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement);
+ const current = KeyframeFunc.convertPixelTime(this._currentBarX, 'mili', 'time', this._tickSpacing, this._tickIncrement);
return this.toReadTime(current > this._time ? this._time : current);
- }
+ };
@observable private mapOfTracks: (Track | null)[] = [];
@@ -465,22 +500,22 @@ export class Timeline extends React.Component<FieldViewProps> {
}
});
return longestTime;
- }
+ };
@action
toAuthoring = () => {
this._time = Math.ceil((this.findLongestTime() ?? 1) / 100000) * 100000;
- this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement);
- }
+ this._totalLength = KeyframeFunc.convertPixelTime(this._time, 'mili', 'pixel', this._tickSpacing, this._tickIncrement);
+ };
@action
toPlay = () => {
this._time = this.findLongestTime();
- this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement);
- }
+ this._totalLength = KeyframeFunc.convertPixelTime(this._time, 'mili', 'pixel', this._tickSpacing, this._tickIncrement);
+ };
/**
- * if you have any question here, just shoot me an email or text.
+ * if you have any question here, just shoot me an email or text.
* basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region)
*/
render() {
@@ -488,23 +523,46 @@ export class Timeline extends React.Component<FieldViewProps> {
// change visible and total width
return (
- <div style={{ visibility: "visible" }}>
- <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}>
+ <div style={{ visibility: 'visible' }}>
+ <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}>
<div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}>
<div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}>
{this.drawTicks()}
<div key="timeline_scrubber" className="scrubber" style={{ transform: `translate(${this._currentBarX}px)` }}>
- <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown} ></div>
+ <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown}></div>
</div>
<div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
- {this.children.map(doc =>
- <Track ref={ref => this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} />
- )}
+ {this.children.map(doc => (
+ <Track
+ ref={ref => this.mapOfTracks.push(ref)}
+ node={doc}
+ currentBarX={this._currentBarX}
+ changeCurrentBarX={this.changeCurrentBarX}
+ transform={this.props.ScreenToLocalTransform()}
+ time={this._time}
+ tickSpacing={this._tickSpacing}
+ tickIncrement={this._tickIncrement}
+ collection={this.props.Document}
+ timelineVisible={true}
+ />
+ ))}
</div>
</div>
<div className="currentTime">Current: {this.getCurrentTime()}</div>
<div key="timeline_title" className="title-container" ref={this._titleContainer}>
- {this.children.map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)}
+ {this.children.map(doc => (
+ <div
+ style={{ height: `${this._titleHeight}px` }}
+ className="datapane"
+ onPointerOver={() => {
+ Doc.BrushDoc(doc);
+ }}
+ onPointerOut={() => {
+ Doc.UnBrushDoc(doc);
+ }}>
+ <p>{StrCast(doc.title)}</p>
+ </div>
+ ))}
</div>
<div key="timeline_resize" onPointerDown={this.onResizeDown}>
<FontAwesomeIcon className="resize" icon={faGripLines as IconLookup} />
@@ -515,4 +573,4 @@ export class Timeline extends React.Component<FieldViewProps> {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 467c2893f..abb4b6bc6 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -44,14 +44,14 @@ export class CollectionCarouselView extends CollectionSubView() {
@computed get content() {
const index = NumCast(this.layoutDoc._itemIndex);
const curDoc = this.childLayoutPairs?.[index];
- const captionProps = { ...this.props, fieldKey: "caption" };
+ const captionProps = { ...OmitKeys(this.props, ["setHeight",]).omit, fieldKey: "caption" };
const marginX = NumCast(this.layoutDoc["caption-xMargin"]);
const marginY = NumCast(this.layoutDoc["caption-yMargin"]);
const showCaptions = StrCast(this.layoutDoc._showCaption);
return !(curDoc?.layout instanceof Doc) ? (null) :
<>
<div className="collectionCarouselView-image" key="image">
- <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit}
+ <DocumentView {...OmitKeys(this.props, ["setHeight", "NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit}
onDoubleClick={this.onContentDoubleClick}
onClick={this.onContentClick}
hideCaptions={showCaptions ? true : false}
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index b2ee33807..091ba8e74 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,5 +1,6 @@
-@import "../global/globalCssVariables.scss";
-
+@import '../global/globalCssVariables.scss';
+@import '../../../../node_modules/golden-layout/src/css/goldenlayout-base.css';
+@import '../../../../node_modules/golden-layout/src/css/goldenlayout-dark-theme.css';
.lm_title {
-webkit-appearance: none;
@@ -36,11 +37,11 @@
}
.lm_header .lm_tab {
- padding: 0px;
- opacity: 0.7;
- box-shadow: none;
- height: 24px;
- // border-bottom: 1px black;
+ // padding: 0px; // moved to MainView.scss, othwerise they get overridden by default stylings
+ // opacity: 0.7;
+ // box-shadow: none;
+ // height: 25px;
+ // border-bottom: black solid;
.collectionDockingView-gear {
display: none;
@@ -153,7 +154,7 @@
background: $white;
}
- .lm_controls>li {
+ .lm_controls > li {
opacity: 1;
transform: scale(1);
}
@@ -196,7 +197,7 @@
position: absolute;
cursor: move;
border: 2px solid #cfe8ff;
- box-shadow: inset 0 0 60px rgba(0, 0, 0, .2);
+ box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.2);
border-radius: 5px;
z-index: 1000;
box-sizing: border-box;
@@ -205,7 +206,7 @@
.flexlayout__outline_rect_edge {
cursor: move;
border: 2px solid #b7d1b5;
- box-shadow: inset 0 0 60px rgba(0, 0, 0, .2);
+ box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.2);
border-radius: 5px;
z-index: 1000;
box-sizing: border-box;
@@ -214,7 +215,7 @@
.flexlayout__edge_rect {
position: absolute;
z-index: 1000;
- box-shadow: inset 0 0 5px rgba(0, 0, 0, .2);
+ box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background-color: lightgray;
}
@@ -222,7 +223,7 @@
position: absolute;
cursor: move;
border: 2px solid #aaaaaa;
- box-shadow: inset 0 0 60px rgba(0, 0, 0, .3);
+ box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.3);
border-radius: 5px;
z-index: 1000;
box-sizing: border-box;
@@ -301,7 +302,7 @@
.flexlayout__tab_button:hover .flexlayout__tab_button_trailing,
.flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
- background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
+ background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center;
}
.flexlayout__tab_button_overflow {
@@ -314,7 +315,7 @@
font-size: 10px;
color: lightgray;
font-family: Arial, sans-serif;
- background: transparent url("../../../../node_modules/flexlayout-react/images/more.png") no-repeat left;
+ background: transparent url('../../../../node_modules/flexlayout-react/images/more.png') no-repeat left;
}
.flexlayout__tabset_header {
@@ -369,7 +370,7 @@
height: 20px;
border: none;
outline-width: 0;
- background: transparent url("../../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center;
+ background: transparent url('../../../../node_modules/flexlayout-react/images/maximize.png') no-repeat center;
}
.flexlayout__tab_toolbar_button-max {
@@ -377,7 +378,7 @@
height: 20px;
border: none;
outline-width: 0;
- background: transparent url("../../../../node_modules/flexlayout-react/images/restore.png") no-repeat center;
+ background: transparent url('../../../../node_modules/flexlayout-react/images/restore.png') no-repeat center;
}
.flexlayout__popup_menu_item {
@@ -390,7 +391,7 @@
}
.flexlayout__popup_menu_container {
- box-shadow: inset 0 0 5px rgba(0, 0, 0, .15);
+ box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.15);
border: 1px solid #555;
background: #222;
border-radius: 3px;
@@ -497,7 +498,7 @@
.flexlayout__border_button:hover .flexlayout__border_button_trailing,
.flexlayout__border_button--selected .flexlayout__border_button_trailing {
- background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
+ background: transparent url('../../../../node_modules/flexlayout-react/images/close_white.png') no-repeat center;
}
.flexlayout__border_toolbar_left {
@@ -539,4 +540,4 @@
bottom: 0;
right: 0;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9e8374605..39e2cc17d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,35 +1,35 @@
-import 'golden-layout/src/css/goldenlayout-base.css';
-import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import * as GoldenLayout from "../../../client/goldenLayout";
-import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc";
+import * as GoldenLayout from '../../../client/goldenLayout';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
-import { listSpec } from '../../../fields/Schema';
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
import { inheritParentAcls } from '../../../fields/util';
-import { DocServer } from "../../DocServer";
+import { emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DragManager } from "../../util/DragManager";
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
import { InteractionUtils } from '../../util/InteractionUtils';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { SelectionManager } from '../../util/SelectionManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { DashboardView } from '../DashboardView';
import { LightboxView } from '../LightboxView';
-import "./CollectionDockingView.scss";
-import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
-import { CollectionViewType } from './CollectionView';
+import './CollectionDockingView.scss';
+import { CollectionFreeFormView } from './collectionFreeForm';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { TabDocView } from './TabDocView';
-import React = require("react");
-const _global = (window /* browser */ || global /* node */) as any;
+import React = require('react');
+const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class CollectionDockingView extends CollectionSubView() {
- @observable public static Instance: CollectionDockingView;
+ @observable public static Instance: CollectionDockingView | undefined;
public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
return {
type: 'react-component',
@@ -38,53 +38,79 @@ export class CollectionDockingView extends CollectionSubView() {
width: width,
props: {
documentId: document[Id],
- panelName // name of tab that can be used to close or replace its contents
- }
+ panelName, // name of tab that can be used to close or replace its contents
+ },
};
}
private _reactionDisposer?: IReactionDisposer;
private _lightboxReactionDisposer?: IReactionDisposer;
private _containerRef = React.createRef<HTMLDivElement>();
- private _flush: UndoManager.Batch | undefined;
- private _ignoreStateChange = "";
+ public _flush: UndoManager.Batch | undefined;
+ private _ignoreStateChange = '';
public tabMap: Set<any> = new Set();
- public get initialized() { return this._goldenLayout !== null; }
- public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; }
- @observable private _goldenLayout: any = null;
+ public get HasFullScreen() {
+ return this._goldenLayout._maximisedItem !== null;
+ }
+ private _goldenLayout: any = null;
constructor(props: SubCollectionViewProps) {
super(props);
- runInAction(() => CollectionDockingView.Instance = this);
+ runInAction(() => (CollectionDockingView.Instance = this));
//Why is this here?
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
DragManager.StartWindowDrag = this.StartOtherDrag;
}
- public StartOtherDrag = (e: any, dragDocs: Doc[]) => {
- !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag"));
- const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) :
- { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) };
- const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config);
- //dragSource._dragListener.on("dragStop", dragSource.destroy);
- dragSource._dragListener.onMouseDown(e);
- }
-
+ /**
+ * Switches from dragging a document around a freeform canvas to dragging it as a tab to be docked.
+ *
+ * @param e fake mouse down event position data containing pageX and pageY coordinates
+ * @param dragDocs the documents to be dragged
+ * @param batch optionally an undo batch that has been started to use instead of starting a new batch
+ */
+ public StartOtherDrag = (e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => {
+ this._flush = this._flush ?? UndoManager.StartBatch('golden layout drag');
+ const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) };
+ const dragSource = this._goldenLayout.createDragSource(document.createElement('div'), config);
+ this.tabDragStart(dragSource, finishDrag);
+ dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 });
+ };
+
+ tabItemDropped = () => DragManager.CompleteWindowDrag?.(false);
+ tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => {
+ const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc;
+ dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc]));
+ DragManager.CompleteWindowDrag = (aborted: boolean) => {
+ if (aborted) {
+ proxy._dragListener.AbortDrag();
+ if (this._flush) {
+ this._flush.cancel(); // cancel the undo change being logged
+ this._flush = undefined;
+ this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout)
+ }
+ DragManager.CompleteWindowDrag = undefined;
+ }
+ finishDrag?.(aborted);
+ };
+ };
@undoBatch
public CloseFullScreen = () => {
this._goldenLayout._maximisedItem?.toggleMaximise();
this.stateChanged();
- }
+ };
@undoBatch
public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
- const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document);
- if (tab) {
- const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
- if (j !== -1) {
- tab.header.parent.contentItems[j].remove();
- return CollectionDockingView.Instance.layoutChanged();
+ if (CollectionDockingView.Instance) {
+ const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+ if (tab) {
+ const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
+ if (j !== -1) {
+ tab.header.parent.contentItems[j].remove();
+ return CollectionDockingView.Instance.layoutChanged();
+ }
}
}
@@ -93,21 +119,23 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
public static OpenFullScreen(doc: Doc) {
+ SelectionManager.DeselectAll();
const instance = CollectionDockingView.Instance;
- if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
- return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+ if (instance) {
+ if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') {
+ return DashboardView.openDashboard(doc);
+ }
+ const newItemStackConfig = {
+ type: 'stack',
+ content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))],
+ };
+ const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+ instance._goldenLayout.root.contentItems[0].addChild(docconfig);
+ docconfig.callDownwards('_$init');
+ instance._goldenLayout._$maximiseItem(docconfig);
+ instance._goldenLayout.emit('stateChanged');
+ instance.stateChanged();
}
- const newItemStackConfig = {
- type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))]
- };
- const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
- instance._goldenLayout.root.contentItems[0].addChild(docconfig);
- docconfig.callDownwards('_$init');
- instance._goldenLayout._$maximiseItem(docconfig);
- instance._goldenLayout.emit('stateChanged');
- instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
- instance.stateChanged();
return true;
}
@@ -122,23 +150,23 @@ export class CollectionDockingView extends CollectionSubView() {
const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
stack.addChild(newContentItem.contentItems[0], undefined);
stack.contentItems[activeContentItemIndex].remove();
- return CollectionDockingView.Instance.layoutChanged();
+ return instance.layoutChanged();
}
- const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => tab.contentItem.config.props.panelName === panelName);
+ const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
if (tab) {
tab.header.parent.addChild(newConfig, undefined);
const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
!addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
- return CollectionDockingView.Instance.layoutChanged();
+ return instance.layoutChanged();
}
return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
}
-
@undoBatch
public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) {
- return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex((tab) => tab.DashDoc === doc) !== -1 ?
- CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName);
+ return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
+ ? CollectionDockingView.CloseSplit(doc)
+ : CollectionDockingView.AddSplit(doc, location, stack, panelName);
}
//
@@ -147,104 +175,98 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
@action
public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
- if (document.type === DocumentType.PRES) {
- const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []);
- if (docs.includes(document)) {
- docs.splice(docs.indexOf(document), 1);
- }
- }
- if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
-
+ if (document._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
+ if (!CollectionDockingView.Instance) return false;
const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
if (tab) {
tab.header.parent.setActiveContentItem(tab.contentItem);
return true;
}
const instance = CollectionDockingView.Instance;
+ const glayRoot = instance._goldenLayout.root;
if (!instance) return false;
- else {
- const docList = DocListCast(instance.props.Document[DataSym]["data-all"]);
- // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases
- !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document);
- // adds an alias of the doc to the data-all field of the layoutdocs of the aliases
- DocListCast(instance.props.Document[DataSym].aliases).forEach(alias => {
- const aliasDocList = DocListCast(alias["data-all"]);
- // if aliasDocList contains the alias, don't do anything
- // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias
- !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document);
- });
- }
const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
if (!pullSide && stack) {
stack.addChild(docContentConfig, undefined);
stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]);
} else {
- const newItemStackConfig = { type: 'stack', content: [docContentConfig] };
- const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
- if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
- instance._goldenLayout.root.addChild(newContentItem);
- } else if (instance._goldenLayout.root.contentItems[0].isStack) {
- instance._goldenLayout.root.contentItems[0].addChild(docContentConfig);
- } else if (
- instance._goldenLayout.root.contentItems.length === 1 &&
- instance._goldenLayout.root.contentItems[0].contentItems.length === 1 &&
- instance._goldenLayout.root.contentItems[0].contentItems[0].contentItems.length === 0) {
- instance._goldenLayout.root.contentItems[0].contentItems[0].addChild(docContentConfig);
- }
- else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
+ const newContentItem = () => {
+ const newItem = glayRoot.layoutManager.createContentItem({ type: 'stack', content: [docContentConfig] }, instance._goldenLayout);
+ newItem.callDownwards('_$init');
+ return newItem;
+ };
+ if (glayRoot.contentItems.length === 0) {
+ // if no rows / columns
+ glayRoot.addChild(newContentItem());
+ } else if (glayRoot.contentItems[0].isStack) {
+ glayRoot.contentItems[0].addChild(docContentConfig);
+ } else if (glayRoot.contentItems.length === 1 && glayRoot.contentItems[0].contentItems.length === 1 && glayRoot.contentItems[0].contentItems[0].contentItems.length === 0) {
+ glayRoot.contentItems[0].contentItems[0].addChild(docContentConfig);
+ } else if (instance._goldenLayout.root.contentItems[0].isRow) {
+ // if row
switch (pullSide) {
default:
- case "right": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
- case "left": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
- case "top":
- case "bottom":
+ case 'right':
+ glayRoot.contentItems[0].addChild(newContentItem());
+ break;
+ case 'left':
+ glayRoot.contentItems[0].addChild(newContentItem(), 0);
+ break;
+ case 'top':
+ case 'bottom':
// if not going in a row layout, must add already existing content into column
- const rowlayout = instance._goldenLayout.root.contentItems[0];
- const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
+ const rowlayout = glayRoot.contentItems[0];
+ const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout);
- CollectionDockingView.Instance._goldenLayout.saveScrollTops(rowlayout.element);
+ const newItem = newContentItem();
+ instance._goldenLayout.saveScrollTops(rowlayout.element);
rowlayout.parent.replaceChild(rowlayout, newColumn);
- if (pullSide === "top") {
+ if (pullSide === 'top') {
newColumn.addChild(rowlayout, undefined, true);
- newColumn.addChild(newContentItem, 0, true);
- } else if (pullSide === "bottom") {
- newColumn.addChild(newContentItem, undefined, true);
+ newColumn.addChild(newItem, 0, true);
+ } else if (pullSide === 'bottom') {
+ newColumn.addChild(newItem, undefined, true);
newColumn.addChild(rowlayout, 0, true);
}
- CollectionDockingView.Instance._goldenLayout.restoreScrollTops(rowlayout.element);
+ instance._goldenLayout.restoreScrollTops(rowlayout.element);
rowlayout.config.height = 50;
- newContentItem.config.height = 50;
+ newItem.config.height = 50;
}
- } else {// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
+ } else {
+ // if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
switch (pullSide) {
- case "top": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
- case "bottom": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
- case "left":
- case "right":
+ case 'top':
+ glayRoot.contentItems[0].addChild(newContentItem(), 0);
+ break;
+ case 'bottom':
+ glayRoot.contentItems[0].addChild(newContentItem());
+ break;
+ case 'left':
+ case 'right':
default:
// if not going in a row layout, must add already existing content into column
- const collayout = instance._goldenLayout.root.contentItems[0];
- const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+ const collayout = glayRoot.contentItems[0];
+ const newRow = collayout.layoutManager.createContentItem({ type: 'row' }, instance._goldenLayout);
- CollectionDockingView.Instance._goldenLayout.saveScrollTops(collayout.element);
+ const newItem = newContentItem();
+ instance._goldenLayout.saveScrollTops(collayout.element);
collayout.parent.replaceChild(collayout, newRow);
- if (pullSide === "left") {
+ if (pullSide === 'left') {
newRow.addChild(collayout, undefined, true);
- newRow.addChild(newContentItem, 0, true);
+ newRow.addChild(newItem, 0, true);
} else {
- newRow.addChild(newContentItem, undefined, true);
+ newRow.addChild(newItem, undefined, true);
newRow.addChild(collayout, 0, true);
}
- CollectionDockingView.Instance._goldenLayout.restoreScrollTops(collayout.element);
+ instance._goldenLayout.restoreScrollTops(collayout.element);
collayout.config.width = 50;
- newContentItem.config.width = 50;
+ newItem.config.width = 50;
}
}
instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
- newContentItem.callDownwards('_$init');
}
return instance.layoutChanged();
@@ -255,16 +277,15 @@ export class CollectionDockingView extends CollectionSubView() {
layoutChanged() {
this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
this._goldenLayout.emit('stateChanged');
- this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
this.stateChanged();
return true;
}
- async setupGoldenLayout() {
+ setupGoldenLayout = async () => {
const config = StrCast(this.props.Document.dockingConfig);
if (config) {
const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
- const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) ?? [];
+ const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
await Promise.all(docids.map(id => DocServer.GetRefField(id)));
if (this._goldenLayout) {
@@ -275,201 +296,267 @@ export class CollectionDockingView extends CollectionSubView() {
this._goldenLayout.unbind('tabCreated', this.tabCreated);
this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
this._goldenLayout.unbind('stackCreated', this.stackCreated);
- } catch (e) { }
- }
- }
- this.tabMap.clear();
- this._goldenLayout?.destroy();
- runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
- this._goldenLayout.on('tabCreated', this.tabCreated);
- this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
- this._goldenLayout.on('stackCreated', this.stackCreated);
- this._goldenLayout.registerComponent('DocumentFrameRenderer', TabDocView);
- this._goldenLayout.container = this._containerRef.current;
- if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
- try {
- this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
- } catch (e) {
- this._goldenLayout.config.maximisedItemId = null;
+ } catch (e) {}
}
+ this.tabMap.clear();
+ this._goldenLayout.destroy();
}
- this._goldenLayout.init();
+ const glay = (this._goldenLayout = new GoldenLayout(JSON.parse(config)));
+ glay.on('tabCreated', this.tabCreated);
+ glay.on('tabDestroyed', this.tabDestroyed);
+ glay.on('stackCreated', this.stackCreated);
+ glay.registerComponent('DocumentFrameRenderer', TabDocView);
+ glay.container = this._containerRef.current;
+ glay.init();
+ glay.root.layoutManager.on('itemDropped', this.tabItemDropped);
+ glay.root.layoutManager.on('dragStart', this.tabDragStart);
+ glay.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
}
- }
+ };
componentDidMount: () => void = () => {
if (this._containerRef.current) {
- this._lightboxReactionDisposer = reaction(() => LightboxView.LightboxDoc, doc => setTimeout(() => !doc && this.onResize(undefined)));
+ this._lightboxReactionDisposer = reaction(
+ () => LightboxView.LightboxDoc,
+ doc => setTimeout(() => !doc && this.onResize(undefined))
+ );
new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
- this._reactionDisposer = reaction(() => StrCast(this.props.Document.dockingConfig),
+ this._reactionDisposer = reaction(
+ () => StrCast(this.props.Document.dockingConfig),
config => {
- if (!this._goldenLayout || this._ignoreStateChange !== config) { // bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
+ if (!this._goldenLayout || this._ignoreStateChange !== config) {
+ // bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
this.setupGoldenLayout();
}
- this._ignoreStateChange = "";
- });
- setTimeout(() => this.setupGoldenLayout(), 0);
+ this._ignoreStateChange = '';
+ }
+ );
+ setTimeout(this.setupGoldenLayout);
//window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
}
- }
+ };
componentWillUnmount: () => void = () => {
try {
this._goldenLayout.unbind('stackCreated', this.stackCreated);
this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- } catch (e) { }
+ } catch (e) {}
this._goldenLayout?.destroy();
window.removeEventListener('resize', this.onResize);
this._reactionDisposer?.();
this._lightboxReactionDisposer?.();
- }
+ };
@action
onResize = (event: any) => {
const cur = this._containerRef.current;
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
!LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height);
- }
+ };
@action
onPointerUp = (e: MouseEvent): void => {
- window.removeEventListener("pointerup", this.onPointerUp);
- if (this._flush) {
- setTimeout(() => {
- CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig());
- this.stateChanged();
- this._flush?.end();
- this._flush = undefined;
- }, 10);
+ window.removeEventListener('pointerup', this.onPointerUp);
+ const flush = this._flush;
+ this._flush = undefined;
+ if (flush) {
+ DragManager.CompleteWindowDrag = undefined;
+ if (!this.stateChanged()) flush.cancel();
+ else flush.end();
}
- }
+ };
@action
onPointerDown = (e: React.PointerEvent): void => {
let hitFlyout = false;
for (let par = e.target as any; !hitFlyout && par; par = par.parentElement) {
- hitFlyout = (par.className === "dockingViewButtonSelector");
+ hitFlyout = par.className === 'dockingViewButtonSelector';
}
if (!hitFlyout) {
- window.addEventListener("mouseup", this.onPointerUp);
- if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) {
- this._flush = UndoManager.StartBatch("golden layout edit");
+ const htmlTarget = e.target as HTMLElement;
+ window.addEventListener('mouseup', this.onPointerUp);
+ if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) {
+ const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : '';
+ if (!className.includes('lm_close') && !className.includes('lm_maximise')) {
+ this._flush = UndoManager.StartBatch('golden layout edit');
+ }
}
}
- if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
- ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
e.stopPropagation();
}
+ };
+
+ public CaptureThumbnail() {
+ const content = this.props.DocumentView?.()?.ContentDiv;
+ if (content) {
+ const _width = Number(getComputedStyle(content).width.replace('px', ''));
+ const _height = Number(getComputedStyle(content).height.replace('px', ''));
+ return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => {
+ const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title + '-icon', _width, _height, _nativeWidth, _nativeHeight });
+ const proto = Cast(img.proto, Doc, null)!;
+ proto['data-nativeWidth'] = _width;
+ proto['data-nativeHeight'] = _height;
+ this.dataDoc.thumb = img;
+ });
+ }
}
-
- public static async Copy(doc: Doc, clone = false) {
- clone = !Doc.UserDoc().noviceMode;
+ public static async TakeSnapshot(doc: Doc | undefined, clone = false) {
+ if (!doc) return undefined;
let json = StrCast(doc.dockingConfig);
if (clone) {
- const cloned = (await Doc.MakeClone(doc));
- Array.from(cloned.map.entries()).map(entry => json = json.replace(entry[0], entry[1][Id]));
+ const cloned = await Doc.MakeClone(doc);
+ Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id])));
Doc.GetProto(cloned.clone).dockingConfig = json;
- return cloned.clone;
+ return DashboardView.openDashboard(cloned.clone);
}
const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
- const origtabids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || [];
- const origtabs = origtabids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
+ const origtabids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) || [];
+ const origtabs = origtabids
+ .map(id => DocServer.GetCachedRefField(id))
+ .filter(f => f)
+ .map(f => f as Doc);
const newtabs = origtabs.map(origtab => {
const origtabdocs = DocListCast(origtab.data);
- const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true) : Doc.MakeAlias(origtab);
+ const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeAlias(origtab);
const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeAlias(origtabdoc));
- newtabdocs.length && (Doc.GetProto(newtab).data = new List<Doc>(newtabdocs));
+ if (newtabdocs.length) {
+ Doc.GetProto(newtab).data = new List<Doc>(newtabdocs);
+ newtabdocs.forEach(ntab => (ntab.context = newtab));
+ }
json = json.replace(origtab[Id], newtab[Id]);
return newtab;
});
- return Docs.Create.DockDocument(newtabs, json, { title: "Snapshot: " + doc.title });
+ const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
+ return DashboardView.openDashboard(await copy);
}
@action
stateChanged = () => {
+ this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
const json = JSON.stringify(this._goldenLayout.toConfig());
const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
- const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
- const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
-
- this.props.Document.dockingConfig = json;
- setTimeout(async () => {
- const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
- const tabs = sublists && Cast(sublists[0], Doc, null);
- // const other = sublists && Cast(sublists[1], Doc, null);
- const tabdocs = await DocListCastAsync(tabs?.data);
- // const otherdocs = await DocListCastAsync(other?.data);
- if (tabs) {
- tabs.data = new List<Doc>(docs);
- // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List<Doc>(docs)));
- }
- // const otherSet = new Set<Doc>();
- // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
- // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
- // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
- // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List<Doc>([...docs, ...vals]);
- // if (other) {
- // other.data = new List<Doc>(vals);
- // // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List<Doc>(vals)));
- // }
- }, 0);
- }
+ const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
+ const docs = !docids
+ ? []
+ : docids
+ .map(id => DocServer.GetCachedRefField(id))
+ .filter(f => f)
+ .map(f => f as Doc);
+ const changesMade = this.props.Document.dockcingConfig !== json;
+ if (changesMade && !this._flush) {
+ UndoManager.RunInBatch(() => {
+ this.props.Document.dockingConfig = json;
+ this.props.Document.data = new List<Doc>(docs);
+ }, 'state changed');
+ }
+ return changesMade;
+ };
tabDestroyed = (tab: any) => {
- this.tabMap.delete(tab);
- tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
- tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
- }
+ if (tab.DashDoc?.type !== DocumentType.KVP) {
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
+ Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
+ }
+ if (CollectionDockingView.Instance) {
+ const dview = CollectionDockingView.Instance.props.Document;
+ const fieldKey = CollectionDockingView.Instance.props.fieldKey;
+ Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
+ this.tabMap.delete(tab);
+ tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
+ tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
+ setTimeout(this.stateChanged);
+ }
+ };
tabCreated = (tab: any) => {
- tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
- }
+ this.tabMap.add(tab);
+ tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
+ };
stackCreated = (stack: any) => {
stack.header?.element.on('mousedown', (e: any) => {
- if (e.target === stack.header?.element[0] && e.button === 2) {
- const emptyPane = CurrentUserUtils.EmptyPane;
- emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
+ const dashboard = Doc.ActiveDashboard;
+ if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
+ dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
+ _width: this.props.PanelWidth(),
+ _height: this.props.PanelHeight(),
+ _backgroundGridShow: true,
+ _fitWidth: true,
+ title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
- CollectionDockingView.AddSplit(docToAdd, "", stack);
+ CollectionDockingView.AddSplit(docToAdd, '', stack);
}
});
- stack.header?.controlsContainer.find('.lm_close') //get the close icon
+ stack.header?.controlsContainer
+ .find('.lm_close') //get the close icon
.off('click') //unbind the current click handler
- .click(action(() => {
- //if (confirm('really close this?')) {
- if (!stack.parent.parent.isRoot || stack.parent.contentItems.length > 1) {
- stack.remove();
- stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true));
- } else {
- alert('cant delete the last stack');
- }
- }));
- stack.header?.controlsContainer.find('.lm_popout') //get the close icon
+ .click(
+ action(() => {
+ //if (confirm('really close this?')) {
+ if (!stack.parent.parent.isRoot || stack.parent.contentItems.length > 1) {
+ stack.remove();
+ } else {
+ alert('cant delete the last stack');
+ }
+ })
+ );
+
+ stack.header?.controlsContainer
+ .find('.lm_maximise') //get the close icon
+ .click(() => setTimeout(this.stateChanged));
+ stack.header?.controlsContainer
+ .find('.lm_popout') //get the popout icon
.off('click') //unbind the current click handler
- .click(action(() => {
- // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size
- const emptyPane = CurrentUserUtils.EmptyPane;
- emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
- const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
- });
- this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
- CollectionDockingView.AddSplit(docToAdd, "", stack);
- }));
- }
+ .click(
+ action(() => {
+ // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size
+ const dashboard = Doc.ActiveDashboard;
+ if (dashboard) {
+ dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
+ const docToAdd = Docs.Create.FreeformDocument([], {
+ _width: this.props.PanelWidth(),
+ _height: this.props.PanelHeight(),
+ _fitWidth: true,
+ _backgroundGridShow: true,
+ title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
+ });
+ this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+ CollectionDockingView.AddSplit(docToAdd, '', stack);
+ }
+ })
+ );
+ };
render() {
return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} />;
}
}
-ScriptingGlobals.add(function openInLightbox(doc: any) { LightboxView.AddDocTab(doc, "lightbox"); },
- "opens up document in a lightbox", "(doc: any)");
-ScriptingGlobals.add(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); },
- "opens up document in tab on right side of the screen", "(doc: any)");
-ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); }); \ No newline at end of file
+ScriptingGlobals.add(
+ function openInLightbox(doc: any) {
+ LightboxView.AddDocTab(doc, 'lightbox');
+ },
+ 'opens up document in a lightbox',
+ '(doc: any)'
+);
+ScriptingGlobals.add(
+ function openOnRight(doc: any) {
+ return CollectionDockingView.AddSplit(doc, 'right');
+ },
+ 'opens up document in tab on right side of the screen',
+ '(doc: any)'
+);
+ScriptingGlobals.add(
+ function openInOverlay(doc: any) {
+ return Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+ },
+ 'opens up document in screen overlay layer',
+ '(doc: any)'
+);
+ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) {
+ CollectionDockingView.ReplaceTab(doc, 'right', undefined, shiftKey);
+});
diff --git a/src/client/views/collections/CollectionDockingViewMenu.scss b/src/client/views/collections/CollectionDockingViewMenu.scss
deleted file mode 100644
index 4157f0f7e..000000000
--- a/src/client/views/collections/CollectionDockingViewMenu.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-
-.dockingViewButtonSelector {
- div {
- overflow: visible !important;
- }
-
- display: inline-block;
- width:100%;
- height:100%;
-}
-.dockingViewButtonSelector-flyout {
- position: relative;
- z-index: 9999;
- background-color: #eeeeee;
- box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
- color: black;
-
- padding: 10px;
- border-radius: 3px;
- display: inline-block;
- height: 100%;
- width: 100%;
- border-radius: 3px;
-
- hr {
- height: 1px;
- margin: 0px;
- background-color: gray;
- border-top: 0px;
- border-bottom: 0px;
- border-right: 0px;
- border-left: 0px;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingViewMenu.tsx b/src/client/views/collections/CollectionDockingViewMenu.tsx
deleted file mode 100644
index 1cab293a8..000000000
--- a/src/client/views/collections/CollectionDockingViewMenu.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { DocumentButtonBar } from "../DocumentButtonBar";
-import { DocumentView } from "../nodes/DocumentView";
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-@observer
-export class CollectionDockingViewMenu extends React.Component<{ views: () => DocumentView[], Stack: any }> {
- customStylesheet(styles: any) {
- return {
- ...styles,
- panel: {
- ...styles.panel,
- minWidth: "100px"
- },
- };
- }
- _ref = React.createRef<HTMLDivElement>();
-
- @computed get flyout() {
- return (
- <div className="dockingViewButtonSelector-flyout" title=" " ref={this._ref}>
- <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
- </div>
- );
- }
-
- @observable _tooltipOpen: boolean = false;
- render() {
- return <Tooltip open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
- <span className="dockingViewButtonSelector"
- onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}
- onPointerDown={action(e => {
- this.props.views()[0]?.select(false);
- this._tooltipOpen = false;
- })} >
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
- <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
- </Flyout>
- </span>
- </Tooltip >;
- }
-}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 9d2899f81..cfbcec2d6 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,46 +1,46 @@
-import React = require("react");
+import React = require('react');
import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import { ColorState } from "react-color";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { Document } from "../../../fields/documentSchemas";
-import { Id } from "../../../fields/FieldSymbols";
-import { InkTool } from "../../../fields/InkField";
-import { List } from "../../../fields/List";
-import { ObjectField } from "../../../fields/ObjectField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { listSpec } from "../../../fields/Schema";
-import { ScriptField } from "../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DragManager } from "../../util/DragManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
-import { EditableView } from "../EditableView";
-import { GestureOverlay } from "../GestureOverlay";
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
-import { LightboxView } from "../LightboxView";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentView } from "../nodes/DocumentView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
-import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
-import { PresBox } from "../nodes/trails/PresBox";
-import { DefaultStyleProvider } from "../StyleProvider";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { CollectionLinearView } from "./collectionLinear";
-import "./CollectionMenu.scss";
-import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { TabDocView } from "./TabDocView";
-import { Colors } from "../global/globalEnums";
+import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { ColorState } from 'react-color';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Document } from '../../../fields/documentSchemas';
+import { Id } from '../../../fields/FieldSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
+import { ObjectField } from '../../../fields/ObjectField';
+import { RichTextField } from '../../../fields/RichTextField';
+import { listSpec } from '../../../fields/Schema';
+import { ScriptField } from '../../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { AntimodeMenu } from '../AntimodeMenu';
+import { EditableView } from '../EditableView';
+import { GestureOverlay } from '../GestureOverlay';
+import { Colors } from '../global/globalEnums';
+import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
+import { LightboxView } from '../LightboxView';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { PresBox } from '../nodes/trails/PresBox';
+import { DefaultStyleProvider } from '../StyleProvider';
+import { CollectionDockingView } from './CollectionDockingView';
+import { CollectionLinearView } from './collectionLinear';
+import './CollectionMenu.scss';
+import { COLLECTION_BORDER_WIDTH } from './CollectionView';
+import { TabDocView } from './TabDocView';
interface CollectionMenuProps {
panelHeight: () => number;
@@ -48,7 +48,7 @@ interface CollectionMenuProps {
}
@observer
-export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
+export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
@observable static Instance: CollectionMenu;
@observable SelectedCollection: DocumentView | undefined;
@@ -58,16 +58,18 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
constructor(props: any) {
super(props);
- this.FieldKey = "";
- runInAction(() => CollectionMenu.Instance = this);
+ this.FieldKey = '';
+ runInAction(() => (CollectionMenu.Instance = this));
this._canFade = false; // don't let the inking menu fade away
- runInAction(() => this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true));
+ runInAction(() => (this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true)));
this.jumpTo(300, 300);
}
componentDidMount() {
- reaction(() => SelectionManager.Views().length && SelectionManager.Views()[0],
- view => view && this.SetSelection(view));
+ reaction(
+ () => SelectionManager.Views().length && SelectionManager.Views()[0],
+ view => view && this.SetSelection(view)
+ );
}
@action
@@ -77,86 +79,87 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
@action
toggleMenuPin = (e: React.MouseEvent) => {
- Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
+ Doc.UserDoc()['menuCollections-pinned'] = this.Pinned = !this.Pinned;
if (!this.Pinned && this._left < 0) {
this.jumpTo(300, 300);
}
- }
+ };
@action
- toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth > 0) {
- CurrentUserUtils.propertiesWidth = 0;
+ toggleTopBar = () => {
+ if (SettingsManager.headerBarHeight > 0) {
+ SettingsManager.headerBarHeight = 0;
} else {
- CurrentUserUtils.propertiesWidth = 250;
+ SettingsManager.headerBarHeight = 60;
}
- }
+ };
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
return new Transform(-translateX, -translateY, 1 / scale);
- }
+ };
@computed get contMenuButtons() {
- const selDoc = Doc.UserDoc().contextMenuBtns;
- return !(selDoc instanceof Doc) ? (null) : <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }} >
- <CollectionLinearView
- Document={selDoc}
- DataDoc={undefined}
- fieldKey={"data"}
- dropAction={"alias"}
- setHeight={returnFalse}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- rootSelected={returnTrue}
- bringToFront={emptyFunction}
- select={emptyFunction}
- isContentActive={returnTrue}
- isAnyChildContentActive={returnFalse}
- isSelected={returnFalse}
- docViewPath={returnEmptyDoclist}
- moveDocument={returnFalse}
- CollectionView={undefined}
- addDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- removeDocument={returnFalse}
- ScreenToLocalTransform={this.buttonBarXf}
- PanelWidth={this.props.panelWidth}
- PanelHeight={this.props.panelHeight}
- renderDepth={0}
- focus={emptyFunction}
- whenChildContentsActiveChanged={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- </div>;
+ const selDoc = Doc.MyContextMenuBtns;
+ return !(selDoc instanceof Doc) ? null : (
+ <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }}>
+ <CollectionLinearView
+ Document={selDoc}
+ DataDoc={undefined}
+ fieldKey={'data'}
+ dropAction={'alias'}
+ setHeight={returnFalse}
+ styleProvider={DefaultStyleProvider}
+ rootSelected={returnTrue}
+ bringToFront={emptyFunction}
+ select={emptyFunction}
+ isContentActive={returnTrue}
+ isAnyChildContentActive={returnFalse}
+ isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ moveDocument={returnFalse}
+ CollectionView={undefined}
+ addDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={this.buttonBarXf}
+ PanelWidth={this.props.panelWidth}
+ PanelHeight={this.props.panelHeight}
+ renderDepth={0}
+ focus={emptyFunction}
+ whenChildContentsActiveChanged={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
+ );
}
render() {
+ const propIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
+ const propTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
- const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left";
- const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel";
-
- const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom">
- <div className="collectionMenu-hardCodedButton"
- style={{ backgroundColor: CurrentUserUtils.propertiesWidth > 0 ? Colors.MEDIUM_BLUE : undefined }}
- key="properties"
- onPointerDown={this.toggleProperties}>
- <FontAwesomeIcon icon={propIcon} size="lg" />
- </div>
- </Tooltip>;
+ const prop = (
+ <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="topar" placement="bottom">
+ <div className="collectionMenu-hardCodedButton" style={{ backgroundColor: SettingsManager.propertiesWidth > 0 ? Colors.MEDIUM_BLUE : undefined }} onPointerDown={this.toggleTopBar}>
+ <FontAwesomeIcon icon={propIcon} size="lg" />
+ </div>
+ </Tooltip>
+ );
// NEW BUTTONS
//dash col linear view buttons
- const contMenuButtons =
+ const contMenuButtons = (
<div className="collectionMenu-container">
{this.contMenuButtons}
{prop}
- </div>;
+ </div>
+ );
return contMenuButtons;
@@ -166,7 +169,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
// </button>
// </Tooltip>;
- // OLD BUTTONS
+ // //OLD BUTTONS
// return this.getElement(!this.SelectedCollection ? [/*button*/] :
// [<CollectionViewBaseChrome key="chrome"
// docView={this.SelectedCollection}
@@ -189,49 +192,62 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
export class CollectionViewBaseChrome extends React.Component<CollectionViewMenuProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
- get document() { return this.props.docView?.props.Document; }
- get target() { return this.document; }
+ get document() {
+ return this.props.docView?.props.Document;
+ }
+ get target() {
+ return this.document;
+ }
_templateCommand = {
- params: ["target", "source"], title: "item view",
- script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])",
+ params: ['target', 'source'],
+ title: 'item view',
+ script: 'self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])',
immediate: undoBatch((source: Doc[]) => {
let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text;
- try { formatStr && JSON.parse(formatStr); } catch (e) { formatStr = ""; }
+ try {
+ formatStr && JSON.parse(formatStr);
+ } catch (e) {
+ formatStr = '';
+ }
if (source.length === 1 && formatStr) {
- Doc.SetInPlace(this.target, "childLayoutString", formatStr, false);
+ Doc.SetInPlace(this.target, 'childLayoutString', formatStr, false);
} else if (source.length) {
this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]);
} else {
- Doc.SetInPlace(this.target, "childLayoutString", undefined, true);
- Doc.SetInPlace(this.target, "childLayoutTemplate", undefined, true);
+ Doc.SetInPlace(this.target, 'childLayoutString', undefined, true);
+ Doc.SetInPlace(this.target, 'childLayoutTemplate', undefined, true);
}
}),
initialize: emptyFunction,
};
_narrativeCommand = {
- params: ["target", "source"], title: "child click view",
- script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])",
+ params: ['target', 'source'],
+ title: 'child click view',
+ script: 'self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])',
immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_contentCommand = {
- params: ["target", "source"], title: "set content",
- script: "getProto(self.target).data = copyField(self.source);",
- immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)),
+ params: ['target', 'source'],
+ title: 'set content',
+ script: 'getProto(self.target).data = copyField(self.source);',
+ immediate: undoBatch((source: Doc[]) => (Doc.GetProto(this.target).data = new List<Doc>(source))),
initialize: emptyFunction,
};
_onClickCommand = {
- params: ["target", "proxy"], title: "copy onClick",
+ params: ['target', 'proxy'],
+ title: 'copy onClick',
script: `{ if (self.proxy?.[0]) {
getProto(self.proxy[0]).onClick = copyField(self.target.onClick);
getProto(self.proxy[0]).target = self.target.target;
getProto(self.proxy[0]).source = copyField(self.target.source);
}}`,
- immediate: undoBatch((source: Doc[]) => { }),
+ immediate: undoBatch((source: Doc[]) => {}),
initialize: emptyFunction,
};
_openLinkInCommand = {
- params: ["target", "container"], title: "link follow target",
+ params: ['target', 'container'],
+ title: 'link follow target',
script: `{ if (self.container?.length) {
getProto(self.target).linkContainer = self.container[0];
getProto(self.target).isLinkButton = true;
@@ -241,126 +257,187 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
if (container.length) {
Doc.GetProto(this.target).linkContainer = container[0];
Doc.GetProto(this.target).isLinkButton = true;
- Doc.GetProto(this.target).onClick = ScriptField.MakeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
+ Doc.GetProto(this.target).onClick = ScriptField.MakeScript('getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])');
}
}),
initialize: emptyFunction,
};
_viewCommand = {
- params: ["target"], title: "bookmark view",
+ params: ['target'],
+ title: 'bookmark view',
script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale']; gotoFrame(self.target, self['target-currentFrame']);",
- immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; this.target._currentFrame = (this.target._currentFrame === undefined ? undefined : 0); }),
- initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; button['target-currentFrame'] = this.target._currentFrame; },
+ immediate: undoBatch((source: Doc[]) => {
+ this.target._panX = 0;
+ this.target._panY = 0;
+ this.target._viewScale = 1;
+ this.target._currentFrame = this.target._currentFrame === undefined ? undefined : 0;
+ }),
+ initialize: (button: Doc) => {
+ button['target-panX'] = this.target._panX;
+ button['target-panY'] = this.target._panY;
+ button['target-viewScale'] = this.target._viewScale;
+ button['target-currentFrame'] = this.target._currentFrame;
+ },
};
_clusterCommand = {
- params: ["target"], title: "fit content",
- script: "self.target._fitToBox = !self.target._fitToBox;",
- immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
- initialize: emptyFunction
+ params: ['target'],
+ title: 'fit content',
+ script: 'self.target._fitContentsToBox = !self.target._fitContentsToBox;',
+ immediate: undoBatch((source: Doc[]) => (this.target._fitContentsToBox = !this.target._fitContentsToBox)),
+ initialize: emptyFunction,
};
_fitContentCommand = {
- params: ["target"], title: "toggle clusters",
- script: "self.target._useClusters = !self.target._useClusters;",
- immediate: undoBatch((source: Doc[]) => this.target._useClusters = !this.target._useClusters),
- initialize: emptyFunction
+ params: ['target'],
+ title: 'toggle clusters',
+ script: 'self.target._useClusters = !self.target._useClusters;',
+ immediate: undoBatch((source: Doc[]) => (this.target._useClusters = !this.target._useClusters)),
+ initialize: emptyFunction,
};
_saveFilterCommand = {
- params: ["target"], title: "save filter",
+ params: ['target'],
+ title: 'save filter',
script: `self.target._docFilters = compareLists(self['target-docFilters'],self.target._docFilters) ? undefined : copyField(self['target-docFilters']);
self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`,
- immediate: undoBatch((source: Doc[]) => { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }),
+ immediate: undoBatch((source: Doc[]) => {
+ this.target._docFilters = undefined;
+ this.target._searchFilterDocs = undefined;
+ }),
initialize: (button: Doc) => {
- button['target-docFilters'] = (Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) instanceof ObjectField ?
- ObjectField.MakeCopy((Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) as any as ObjectField) : undefined;
- button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined;
+ const activeDash = Doc.ActiveDashboard;
+ if (activeDash) {
+ button['target-docFilters'] = (Doc.MySearcher._docFilters || activeDash._docFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._docFilters || activeDash._docFilters) as any as ObjectField) : undefined;
+ button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined;
+ }
},
};
- @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
- @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
- @computed get _notetaking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
- @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
- @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; }
- @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; }
- @computed get _tree_commands() { return undefined; }
+ @computed get _freeform_commands() {
+ return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+ }
+ @computed get _stacking_commands() {
+ return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand];
+ }
+ @computed get _notetaking_commands() {
+ return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand];
+ }
+ @computed get _masonry_commands() {
+ return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand];
+ }
+ @computed get _schema_commands() {
+ return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand];
+ }
+ @computed get _doc_commands() {
+ return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand];
+ }
+ @computed get _tree_commands() {
+ return undefined;
+ }
private get _buttonizableCommands() {
switch (this.props.type) {
- default: return this._doc_commands;
- case CollectionViewType.Freeform: return this._freeform_commands;
- case CollectionViewType.Tree: return this._tree_commands;
- case CollectionViewType.Schema: return this._schema_commands;
- case CollectionViewType.Stacking: return this._stacking_commands;
- case CollectionViewType.NoteTaking: return this._notetaking_commands;
- case CollectionViewType.Masonry: return this._stacking_commands;
- case CollectionViewType.Time: return this._freeform_commands;
- case CollectionViewType.Carousel: return this._freeform_commands;
- case CollectionViewType.Carousel3D: return this._freeform_commands;
+ default:
+ return this._doc_commands;
+ case CollectionViewType.Freeform:
+ return this._freeform_commands;
+ case CollectionViewType.Tree:
+ return this._tree_commands;
+ case CollectionViewType.Schema:
+ return this._schema_commands;
+ case CollectionViewType.Stacking:
+ return this._stacking_commands;
+ case CollectionViewType.NoteTaking:
+ return this._notetaking_commands;
+ case CollectionViewType.Masonry:
+ return this._stacking_commands;
+ case CollectionViewType.Time:
+ return this._freeform_commands;
+ case CollectionViewType.Carousel:
+ return this._freeform_commands;
+ case CollectionViewType.Carousel3D:
+ return this._freeform_commands;
}
}
private _commandRef = React.createRef<HTMLInputElement>();
private _viewRef = React.createRef<HTMLInputElement>();
- @observable private _currentKey: string = "";
+ @observable private _currentKey: string = '';
componentDidMount = action(() => {
- this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : "");
+ this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : '');
});
@undoBatch
viewChanged = (e: React.ChangeEvent) => {
- const target = this.document !== Doc.UserDoc().sidebar ? this.document : this.document.proto as Doc;
+ const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : (this.document.proto as Doc);
//@ts-ignore
target._viewType = e.target.selectedOptions[0].value;
- }
+ };
commandChanged = (e: React.ChangeEvent) => {
//@ts-ignore
- runInAction(() => this._currentKey = e.target.selectedOptions[0].value);
- }
-
+ runInAction(() => (this._currentKey = e.target.selectedOptions[0].value));
+ };
@action closeViewSpecs = () => {
this.document._facetWidth = 0;
- }
+ };
@computed get subChrome() {
- switch (this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type) { // bcz: ARgh! hack to get menu for tree view outline items
- default: return this.otherSubChrome;
+ switch (
+ this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type // bcz: ARgh! hack to get menu for tree view outline items
+ ) {
+ default:
+ return this.otherSubChrome;
case CollectionViewType.Invalid:
- case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
- case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
- case CollectionViewType.NoteTaking: return (<CollectionNoteTakingViewChrome key="collchrome" {...this.props} />);
- case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />);
- case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />);
- case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Freeform:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />;
+ case CollectionViewType.Stacking:
+ return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
+ case CollectionViewType.NoteTaking:
+ return <CollectionNoteTakingViewChrome key="collchrome" {...this.props} />;
+ case CollectionViewType.Schema:
+ return <CollectionSchemaViewChrome key="collchrome" {...this.props} />;
+ case CollectionViewType.Tree:
+ return <CollectionTreeViewChrome key="collchrome" {...this.props} />;
+ case CollectionViewType.Masonry:
+ return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
case CollectionViewType.Carousel:
- case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" {...this.props} />);
- case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" {...this.props} />);
- case CollectionViewType.Docking: return (<CollectionDockingChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Carousel3D:
+ return <Collection3DCarouselViewChrome key="collchrome" {...this.props} />;
+ case CollectionViewType.Grid:
+ return <CollectionGridViewChrome key="collchrome" {...this.props} />;
+ case CollectionViewType.Docking:
+ return <CollectionDockingChrome key="collchrome" {...this.props} />;
}
}
@computed get otherSubChrome() {
const docType = this.props.docView.Document.type;
switch (docType) {
- default: return (null);
- case DocumentType.IMG: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
- case DocumentType.PDF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
- case DocumentType.INK: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
- case DocumentType.WEB: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
- case DocumentType.VID: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
- case DocumentType.RTF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />);
- case DocumentType.MAP: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />);
+ default:
+ return null;
+ case DocumentType.IMG:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
+ case DocumentType.PDF:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
+ case DocumentType.INK:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
+ case DocumentType.WEB:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
+ case DocumentType.VID:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
+ case DocumentType.RTF:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />;
+ case DocumentType.MAP:
+ return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
}
}
-
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
if (ele) {
this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document);
}
- }
+ };
@undoBatch
@action
@@ -374,120 +451,139 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
}
dragViewDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, (e, down, delta) => {
- const vtype = this.props.type;
- const c = {
- params: ["target"], title: vtype,
- script: `this.target._viewType = '${StrCast(this.props.type)}'`,
- immediate: (source: Doc[]) => this.document._viewType = Doc.getDocTemplate(source?.[0]),
- initialize: emptyFunction,
- };
- DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
- { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
- return true;
- }, emptyFunction, emptyFunction);
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) => {
+ const vtype = this.props.type;
+ const c = {
+ params: ['target'],
+ title: vtype,
+ script: `this.target._viewType = '${StrCast(this.props.type)}'`,
+ immediate: (source: Doc[]) => (this.document._viewType = Doc.getDocTemplate(source?.[0])),
+ initialize: emptyFunction,
+ };
+ DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
+ return true;
+ },
+ emptyFunction,
+ emptyFunction
+ );
+ };
dragCommandDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, (e, down, delta) => {
- this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c =>
- DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
- { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
- return true;
- }, emptyFunction, () => {
- this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
- });
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) => {
+ this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
+ return true;
+ },
+ emptyFunction,
+ () => {
+ this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
+ }
+ );
+ };
@computed get templateChrome() {
- return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
- <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
- <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
- <button className={"antimodeMenu-button"} >
- <FontAwesomeIcon icon="bullseye" size="lg" />
- </button>
- <select
- className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
- {this._buttonizableCommands?.map(cmd =>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
- )}
- </select>
- </div>
- </Tooltip>
- </div>;
+ return (
+ <div className="collectionViewBaseChrome-template" ref={this.createDropTarget}>
+ <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
+ <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <button className={'antimodeMenu-button'}>
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
+ <select className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={'empty'} value={''} />
+ {this._buttonizableCommands?.map(cmd => (
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>
+ {cmd.title}
+ </option>
+ ))}
+ </select>
+ </div>
+ </Tooltip>
+ </div>
+ );
}
@computed get viewModes() {
const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear];
- const isPres: boolean = (this.document && this.document.type === DocumentType.PRES);
- return isPres ? (null) : (<div className="collectionViewBaseChrome-viewModes" >
- <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
- <div className="commandEntry-outerDiv" ref={this._viewRef} onPointerDown={this.dragViewDown}>
- <button className={"antimodeMenu-button"}>
- <FontAwesomeIcon icon="bullseye" size="lg" />
- </button>
- <select
- className="collectionViewBaseChrome-viewPicker"
- onPointerDown={stopPropagation}
- onChange={this.viewChanged}
- value={StrCast(this.props.type)}>
- {Object.values(CollectionViewType).filter(type => !excludedViewTypes.includes(type)).map(type => (
- <option
- key={Utils.GenerateGuid()}
- className="collectionViewBaseChrome-viewOption"
- onPointerDown={stopPropagation}
- value={type}>
- {type[0].toUpperCase() + type.substring(1)}
- </option>
- ))}
- </select>
- </div>
- </Tooltip>
- </div>);
+ const isPres: boolean = this.document && this.document.type === DocumentType.PRES;
+ return isPres ? null : (
+ <div className="collectionViewBaseChrome-viewModes">
+ <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
+ <div className="commandEntry-outerDiv" ref={this._viewRef} onPointerDown={this.dragViewDown}>
+ <button className={'antimodeMenu-button'}>
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
+ <select className="collectionViewBaseChrome-viewPicker" onPointerDown={stopPropagation} onChange={this.viewChanged} value={StrCast(this.props.type)}>
+ {Object.values(CollectionViewType)
+ .filter(type => !excludedViewTypes.includes(type))
+ .map(type => (
+ <option key={Utils.GenerateGuid()} className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value={type}>
+ {type[0].toUpperCase() + type.substring(1)}
+ </option>
+ ))}
+ </select>
+ </div>
+ </Tooltip>
+ </div>
+ );
}
- @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
- @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
+ @computed get selectedDocumentView() {
+ return SelectionManager.Views().lastElement();
+ }
+ @computed get selectedDoc() {
+ return SelectionManager.Docs().lastElement();
+ }
@computed get notACollection() {
if (this.selectedDoc) {
const layoutField = Doc.LayoutField(this.selectedDoc);
- return this.props.type === CollectionViewType.Docking ||
- typeof (layoutField) === "string" && !layoutField?.includes("CollectionView");
- }
- else return false;
+ return this.props.type === CollectionViewType.Docking || (typeof layoutField === 'string' && !layoutField?.includes('CollectionView'));
+ } else return false;
}
@computed
get pinButton() {
const targetDoc = this.selectedDoc;
const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
- return !targetDoc ? (null) : <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top">
- <button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderLeft: "1px solid gray" }}
- onClick={e => TabDocView.PinDoc(targetDoc, { unpin: isPinned })}>
- <FontAwesomeIcon className="colMenu-icon" size="lg" icon="map-pin" />
- </button>
- </Tooltip>;
+ return !targetDoc ? null : (
+ <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? 'Unpin from presentation' : 'Pin to presentation'}</div>} placement="top">
+ <button
+ className="antimodeMenu-button"
+ style={{ backgroundColor: isPinned ? '121212' : undefined, borderLeft: '1px solid gray' }}
+ onClick={e =>
+ TabDocView.PinDoc(targetDoc, {
+ /* unpin: isPinned*/
+ })
+ }>
+ <FontAwesomeIcon className="colMenu-icon" size="lg" icon="map-pin" />
+ </button>
+ </Tooltip>
+ );
}
@undoBatch
@action
startRecording = () => {
- const doc = Docs.Create.ScreenshotDocument("screen recording", { _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" });
- //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
- CollectionDockingView.AddSplit(doc, "right");
- }
+ const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' });
+ //Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+ CollectionDockingView.AddSplit(doc, 'right');
+ };
@computed
get recordButton() {
const targetDoc = this.selectedDoc;
- return <Tooltip key="record" title={<div className="dash-tooltip">{"Capture screen"}</div>} placement="top">
- <button className="antimodeMenu-button"
- onClick={e => this.startRecording()}>
- <div className="recordButtonOutline" style={{}}>
- <div className="recordButtonInner" style={{}}>
+ return (
+ <Tooltip key="record" title={<div className="dash-tooltip">{'Capture screen'}</div>} placement="top">
+ <button className="antimodeMenu-button" onClick={e => this.startRecording()}>
+ <div className="recordButtonOutline" style={{}}>
+ <div className="recordButtonInner" style={{}}></div>
</div>
- </div>
- </button>
- </Tooltip>;
+ </button>
+ </Tooltip>
+ );
}
@undoBatch
@@ -512,7 +608,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
activeDoc.presPinViewY = y;
activeDoc.presPinViewScale = scale;
} else if (targetDoc.type === DocumentType.VID) {
- activeDoc.presPinTimecode = targetDoc._currentTimecode;
activeDoc.presPinView = true;
} else if (targetDoc.type === DocumentType.COMPARISON) {
const width = targetDoc._clipWidth;
@@ -520,21 +615,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
activeDoc.presPinView = true;
}
}
- }
+ };
@computed
get pinWithViewButton() {
- const presPinWithViewIcon = <img src={`/assets/pinWithView.png`} style={{ margin: "auto", width: 19 }} />;
- return !this.selectedDoc ? (null) :
- <Tooltip title={<div className="dash-tooltip">{"Pin with current view"}</div>} placement="top">
- <button className="antimodeMenu-button" style={{ justifyContent: 'center' }}
- onClick={() => this.pinWithView(this.selectedDoc)}>
+ const presPinWithViewIcon = <img src={`/assets/pinWithView.png`} style={{ margin: 'auto', width: 19 }} />;
+ return !this.selectedDoc ? null : (
+ <Tooltip title={<div className="dash-tooltip">{'Pin with current view'}</div>} placement="top">
+ <button className="antimodeMenu-button" style={{ justifyContent: 'center' }} onClick={() => this.pinWithView(this.selectedDoc)}>
{presPinWithViewIcon}
</button>
- </Tooltip>;
+ </Tooltip>
+ );
}
-
@undoBatch
onAlias = () => {
if (this.selectedDoc && this.selectedDocumentView) {
@@ -547,10 +641,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
alias.y = NumCast(this.selectedDoc.y) + 30;
this.selectedDocumentView.props.addDocument?.(alias);
}
- }
+ };
onAliasButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
- }
+ };
@undoBatch
onAliasButtonMoved = (e: PointerEvent) => {
@@ -558,62 +652,72 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
if (contentDiv && this.selectedDoc) {
const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y];
- dragData.defaultDropAction = "alias";
+ dragData.defaultDropAction = 'alias';
dragData.canEmbed = true;
DragManager.StartDocumentDrag([contentDiv], dragData, e.clientX, e.clientY, {
offsetX: offset[0],
offsetY: offset[1],
- hideSource: false
+ hideSource: false,
});
return true;
}
return false;
- }
+ };
@computed
get aliasButton() {
const targetDoc = this.selectedDoc;
- return !targetDoc || targetDoc.type === DocumentType.PRES ? (null) : <Tooltip title={<div className="dash-tooltip">{"Tap or Drag to create an alias"}</div>} placement="top">
- <button className="antimodeMenu-button" onPointerDown={this.onAliasButtonDown} onClick={this.onAlias} style={{ cursor: "drag" }}>
- <FontAwesomeIcon className="colMenu-icon" icon="copy" size="lg" />
- </button>
- </Tooltip>;
+ return !targetDoc || targetDoc.type === DocumentType.PRES ? null : (
+ <Tooltip title={<div className="dash-tooltip">{'Tap or Drag to create an alias'}</div>} placement="top">
+ <button className="antimodeMenu-button" onPointerDown={this.onAliasButtonDown} onClick={this.onAlias} style={{ cursor: 'drag' }}>
+ <FontAwesomeIcon className="colMenu-icon" icon="copy" size="lg" />
+ </button>
+ </Tooltip>
+ );
}
@computed get lightboxButton() {
const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"View in Lightbox"}</div>} placement="top">
- <button className="antimodeMenu-button" onPointerDown={() => {
- const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
- LightboxView.SetLightboxDoc(targetDoc, undefined, docs);
- }}>
- <FontAwesomeIcon className="colMenu-icon" icon="desktop" size="lg" />
- </button>
- </Tooltip>;
+ return !targetDoc ? null : (
+ <Tooltip title={<div className="dash-tooltip">{'View in Lightbox'}</div>} placement="top">
+ <button
+ className="antimodeMenu-button"
+ onPointerDown={() => {
+ const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
+ LightboxView.SetLightboxDoc(targetDoc, undefined, docs);
+ }}>
+ <FontAwesomeIcon className="colMenu-icon" icon="desktop" size="lg" />
+ </button>
+ </Tooltip>
+ );
}
@computed get toggleOverlayButton() {
- return <>
- <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom">
- <button className={"antimodeMenu-button"} key="float"
- style={{
- backgroundColor: this.props.docView.layoutDoc.z ? "121212" : undefined,
- pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "none" : undefined,
- color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "dimgrey" : undefined
- }}
- onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}>
- <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} />
- </button>
- </Tooltip>
- </>;
+ return (
+ <>
+ <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom">
+ <button
+ className={'antimodeMenu-button'}
+ key="float"
+ style={{
+ backgroundColor: this.props.docView.layoutDoc.z ? '121212' : undefined,
+ pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'none' : undefined,
+ color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
+ }}
+ onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}>
+ <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
+ </button>
+ </Tooltip>
+ </>
+ );
}
render() {
return (
- <div className="collectionMenu-cont" >
+ <div className="collectionMenu-cont">
<div className="collectionMenu">
<div className="collectionViewBaseChrome">
- {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes}
+ {this.notACollection || this.props.type === CollectionViewType.Invalid ? null : this.viewModes}
<div className="collectionMenu-divider" key="divider1"></div>
{this.aliasButton}
{/* {this.pinButton} */}
@@ -624,7 +728,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
<div className="collectionMenu-divider" key="divider3"></div>
{this.lightboxButton}
{this.recordButton}
- {!this._buttonizableCommands ? (null) : this.templateChrome}
+ {!this._buttonizableCommands ? null : this.templateChrome}
</div>
</div>
</div>
@@ -635,30 +739,40 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@observer
export class CollectionDockingChrome extends React.Component<CollectionViewMenuProps> {
render() {
- return (null);
+ return null;
}
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionViewMenuProps & { isOverlay: boolean, isDoc?: boolean }> {
+export class CollectionFreeFormViewChrome extends React.Component<CollectionViewMenuProps & { isOverlay: boolean; isDoc?: boolean }> {
public static Instance: CollectionFreeFormViewChrome;
constructor(props: any) {
super(props);
CollectionFreeFormViewChrome.Instance = this;
}
- get document() { return this.props.docView.props.Document; }
+ get document() {
+ return this.props.docView.props.Document;
+ }
@computed get dataField() {
- return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? "-annotations" : "")];
+ return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? '-annotations' : '')];
+ }
+ @computed get childDocs() {
+ return DocListCast(this.dataField);
+ }
+ @computed get selectedDocumentView() {
+ return SelectionManager.Views().lastElement();
+ }
+ @computed get selectedDoc() {
+ return SelectionManager.Docs().lastElement();
+ }
+ @computed get isText() {
+ return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
}
- @computed get childDocs() { return DocListCast(this.dataField); }
- @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
- @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
- @computed get isText() { return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; }
@undoBatch
@action
nextKeyframe = (): void => {
- const currentFrame = Cast(this.document._currentFrame, "number", null);
+ const currentFrame = Cast(this.document._currentFrame, 'number', null);
if (currentFrame === undefined) {
this.document._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -666,236 +780,288 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame));
- }
+ };
@undoBatch
@action
prevKeyframe = (): void => {
- const currentFrame = Cast(this.document._currentFrame, "number", null);
+ const currentFrame = Cast(this.document._currentFrame, 'number', null);
if (currentFrame === undefined) {
this.document._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
- }
+ };
- private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
- private _width = ["1", "5", "10", "100"];
+ private _palette = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', ''];
+ private _width = ['1', '5', '10', '100'];
private _dotsize = [10, 20, 30, 40];
- private _draw = ["∿", "=", "⎯", "→", "↔︎", "ロ", "O"];
- private _head = ["", "", "", "", "arrow", "", ""];
- private _end = ["", "", "", "arrow", "arrow", "", ""];
- private _shapePrims = ["", "", "line", "line", "line", "rectangle", "circle"];
- private _title = ["pen", "highlighter", "line", "line with arrow", "line with double arrows", "square", "circle"];
- private _faName = ["pen-fancy", "highlighter", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
+ private _draw = ['∿', '=', '⎯', '→', '↔︎', 'ロ', 'O'];
+ private _head = ['', '', '', '', 'arrow', '', ''];
+ private _end = ['', '', '', 'arrow', 'arrow', '', ''];
+ private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle'];
+ private _title = ['pen', 'highlighter', 'line', 'line with arrow', 'line with double arrows', 'square', 'circle'];
+ private _faName = ['pen-fancy', 'highlighter', 'minus', 'long-arrow-alt-right', 'arrows-alt-h', 'square', 'circle'];
@observable _selectedPrimitive = this._shapePrims.length;
@observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
@observable _colorBtn = false;
@observable _widthBtn = false;
@observable _fillBtn = false;
- @action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; }
+ @action clearKeepPrimitiveMode() {
+ this._selectedPrimitive = this._shapePrims.length;
+ }
@action primCreated() {
- if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke=
- if (CurrentUserUtils.SelectedTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
- CurrentUserUtils.SelectedTool = InkTool.None;
+ if (!this._keepPrimitiveMode) {
+ //get out of ink mode after each stroke=
+ if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
+ Doc.ActiveTool = InkTool.None;
this._selectedPrimitive = this._shapePrims.length;
- SetActiveArrowStart("none");
- SetActiveArrowEnd("none");
+ SetActiveArrowStart('none');
+ SetActiveArrowEnd('none');
}
}
@action
changeColor = (color: string, type: string) => {
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: "",
+ 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: '',
};
- if (type === "color") {
+ if (type === 'color') {
SetActiveInkColor(Utils.colorString(col));
- } else if (type === "fill") {
+ } else if (type === 'fill') {
SetActiveFillColor(Utils.colorString(col));
}
- }
+ };
@action
editProperties = (value: any, field: string) => {
- SelectionManager.Views().forEach(action((element: DocumentView) => {
- const doc = Document(element.rootDoc);
- if (doc.type === DocumentType.INK) {
- switch (field) {
- case "width": doc.strokeWidth = Number(value); break;
- case "color": doc.color = String(value); break;
- case "fill": doc.fillColor = String(value); break;
- case "dash": doc.strokeDash = value;
+ SelectionManager.Views().forEach(
+ action((element: DocumentView) => {
+ const doc = Document(element.rootDoc);
+ if (doc.type === DocumentType.INK) {
+ switch (field) {
+ case 'width':
+ doc.strokeWidth = Number(value);
+ break;
+ case 'color':
+ doc.color = String(value);
+ break;
+ case 'fill':
+ doc.fillColor = String(value);
+ break;
+ case 'dash':
+ doc.strokeDash = value;
+ }
}
- }
- }));
- }
+ })
+ );
+ };
@computed get drawButtons() {
const func = action((e: React.MouseEvent | React.PointerEvent, i: number, keep: boolean) => {
this._keepPrimitiveMode = keep;
+ // these are for shapes
if (this._selectedPrimitive !== i) {
this._selectedPrimitive = i;
- if (this._title[i] === "highlighter") {
- CurrentUserUtils.SelectedTool = InkTool.Highlighter;
+ if (this._title[i] === 'highlighter') {
+ Doc.ActiveTool = InkTool.Highlighter;
GestureOverlay.Instance.SavedColor = ActiveInkColor();
- SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+ SetActiveInkColor('rgba(245, 230, 95, 0.75)');
} else {
- CurrentUserUtils.SelectedTool = InkTool.Pen;
+ Doc.ActiveTool = InkTool.Pen;
}
SetActiveArrowStart(this._head[i]);
SetActiveArrowEnd(this._end[i]);
- SetActiveBezierApprox("300");
+ SetActiveBezierApprox('300');
GestureOverlay.Instance.InkShape = this._shapePrims[i];
} else {
this._selectedPrimitive = this._shapePrims.length;
- CurrentUserUtils.SelectedTool = InkTool.None;
- SetActiveArrowStart("");
- SetActiveArrowEnd("");
- GestureOverlay.Instance.InkShape = "";
- SetActiveBezierApprox("0");
+ Doc.ActiveTool = InkTool.None;
+ SetActiveArrowStart('');
+ SetActiveArrowEnd('');
+ GestureOverlay.Instance.InkShape = '';
+ SetActiveBezierApprox('0');
}
e.stopPropagation();
});
- return <div className="btn-draw" key="draw">
- {this._draw.map((icon, i) =>
- <Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom">
- <button className="antimodeMenu-button"
- onPointerDown={e => func(e, i, false)}
- onDoubleClick={e => func(e, i, true)}
- style={{ backgroundColor: i === this._selectedPrimitive ? "525252" : "", fontSize: "20" }}>
- <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
- </button>
- </Tooltip>)}
- </div>;
+ return (
+ <div className="btn-draw" key="draw">
+ {this._draw.map((icon, i) => (
+ <Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom">
+ <button className="antimodeMenu-button" onPointerDown={e => func(e, i, false)} onDoubleClick={e => func(e, i, true)} style={{ backgroundColor: i === this._selectedPrimitive ? '525252' : '', fontSize: '20' }}>
+ <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
+ </button>
+ </Tooltip>
+ ))}
+ </div>
+ );
}
- toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => {
- return <Tooltip title={<div className="dash-tooltip">{key}</div>} placement="bottom">
- <button className="antimodeMenu-button" key={key}
- onPointerDown={action(e => setter())}
- style={{ backgroundColor: value ? "121212" : "" }}>
- <FontAwesomeIcon icon={icon} size="lg" />
- {ele}
- </button>
- </Tooltip>;
- }
+ toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps['icon'], ele: JSX.Element | null) => {
+ return (
+ <Tooltip title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+ <button className="antimodeMenu-button" key={key} onPointerDown={action(e => setter())} style={{ backgroundColor: value ? '121212' : '' }}>
+ <FontAwesomeIcon icon={icon} size="lg" />
+ {ele}
+ </button>
+ </Tooltip>
+ );
+ };
@computed get widthPicker() {
- const widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
- return !this._widthBtn ? widthPicker :
+ const widthPicker = this.toggleButton('stroke width', this._widthBtn, () => (this._widthBtn = !this._widthBtn), 'bars', null);
+ return !this._widthBtn ? (
+ widthPicker
+ ) : (
<div className="btn2-group" key="width">
{widthPicker}
- {this._width.map((wid, i) =>
+ {this._width.map((wid, i) => (
<Tooltip title={<div className="dash-tooltip">change width</div>} placement="bottom">
- <button className="antimodeMenu-button" key={wid}
- onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
- style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}>
+ <button
+ className="antimodeMenu-button"
+ key={wid}
+ onPointerDown={action(() => {
+ SetActiveInkWidth(wid);
+ this._widthBtn = false;
+ this.editProperties(wid, 'width');
+ })}
+ style={{ backgroundColor: this._widthBtn ? '121212' : '', zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: 'center' }}>
</button>
- </Tooltip>)}
- </div>;
+ </Tooltip>
+ ))}
+ </div>
+ );
}
@computed get colorPicker() {
- const colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
- <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
- return !this._colorBtn ? colorPicker :
+ const colorPicker = this.toggleButton('stroke color', this._colorBtn, () => (this._colorBtn = !this._colorBtn), 'pen-nib', <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? '121212' }} />);
+ return !this._colorBtn ? (
+ colorPicker
+ ) : (
<div className="btn-group" key="color">
{colorPicker}
- {this._palette.map(color =>
- <button className="antimodeMenu-button" key={color}
- onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
- style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
+ {this._palette.map(color => (
+ <button
+ className="antimodeMenu-button"
+ key={color}
+ onPointerDown={action(() => {
+ this.changeColor(color, 'color');
+ this._colorBtn = false;
+ this.editProperties(color, 'color');
+ })}
+ style={{ backgroundColor: this._colorBtn ? '121212' : '', zIndex: 1001 }}>
{/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
<div className="color-previewII" style={{ backgroundColor: color }}>
- {color === "" ? <p style={{ fontSize: 40, color: "red", marginTop: -10, marginLeft: -5, position: "fixed" }}>☒</p> : ""}
+ {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''}
</div>
- </button >)}
- </div >;
+ </button>
+ ))}
+ </div>
+ );
}
@computed get fillPicker() {
- const fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
- <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
- return !this._fillBtn ? fillPicker :
- <div className="btn-group" key="fill" >
+ const fillPicker = this.toggleButton('shape fill color', this._fillBtn, () => (this._fillBtn = !this._fillBtn), 'fill-drip', <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? '121212' }} />);
+ return !this._fillBtn ? (
+ fillPicker
+ ) : (
+ <div className="btn-group" key="fill">
{fillPicker}
- {this._palette.map(color =>
- <button className="antimodeMenu-button" key={color}
- onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
- style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
+ {this._palette.map(color => (
+ <button
+ className="antimodeMenu-button"
+ key={color}
+ onPointerDown={action(() => {
+ this.changeColor(color, 'fill');
+ this._fillBtn = false;
+ this.editProperties(color, 'fill');
+ })}
+ style={{ backgroundColor: this._fillBtn ? '121212' : '', zIndex: 1001 }}>
<div className="color-previewII" style={{ backgroundColor: color }}>
- {color === "" ? <p style={{ fontSize: 40, color: "red", marginTop: -10, marginLeft: -5, position: "fixed" }}>☒</p> : ""}
+ {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''}
</div>
- </button>)}
-
- </div>;
+ </button>
+ ))}
+ </div>
+ );
}
render() {
- return !this.props.docView.layoutDoc ? (null) :
+ return !this.props.docView.layoutDoc ? null : (
<div className="collectionFreeFormMenu-cont">
<RichTextMenu key="rich" />
- {!this.isText ?
+ {!this.isText ? (
<>
{this.drawButtons}
{this.widthPicker}
{this.colorPicker}
{this.fillPicker}
- {Doc.UserDoc().noviceMode || this.props.isDoc ? (null) :
+ {Doc.noviceMode || this.props.isDoc ? null : (
<>
<Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom">
<div className="backKeyframe" onClick={this.prevKeyframe}>
- <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
</div>
</Tooltip>
<Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
- <div className="numKeyframe" style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? "white" : "black", backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? "#5B9FDD" : "#AEDDF8" }}
- onClick={action(() => this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))} >
+ <div
+ className="numKeyframe"
+ style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? 'white' : 'black', backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? '#5B9FDD' : '#AEDDF8' }}
+ onClick={action(() => this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))}>
{NumCast(this.document._currentFrame)}
</div>
</Tooltip>
<Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom">
<div className="fwdKeyframe" onClick={this.nextKeyframe}>
- <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ <FontAwesomeIcon icon={'caret-right'} size={'lg'} />
</div>
</Tooltip>
- </>}
- </> : (null)
- }
- {!this.selectedDocumentView?.ComponentView?.menuControls ? (null) : this.selectedDocumentView?.ComponentView?.menuControls?.()}
- </div>;
+ </>
+ )}
+ </>
+ ) : null}
+ {!this.selectedDocumentView?.ComponentView?.menuControls ? null : this.selectedDocumentView?.ComponentView?.menuControls?.()}
+ </div>
+ );
}
}
@observer
export class CollectionStackingViewChrome extends React.Component<CollectionViewMenuProps> {
- @observable private _currentKey: string = "";
+ @observable private _currentKey: string = '';
@observable private suggestions: string[] = [];
- get document() { return this.props.docView.props.Document; }
+ get document() {
+ return this.props.docView.props.Document;
+ }
- @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
- @computed get pivotField() { return StrCast(this.document._pivotField); }
+ @computed private get descending() {
+ return StrCast(this.document._columnsSort) === 'descending';
+ }
+ @computed get pivotField() {
+ return StrCast(this.document._pivotField);
+ }
getKeySuggestions = async (value: string): Promise<string[]> => {
const val = value.toLowerCase();
const docs = DocListCast(this.document[this.props.fieldKey]);
- if (Doc.UserDoc().noviceMode) {
+ if (Doc.noviceMode) {
if (docs instanceof Doc) {
- const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
- key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
- (key[0].toUpperCase() === key[0] && key[0] !== "_"));
+ const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
} else {
const keys = new Set<string>();
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
- key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
- (key[0]?.toUpperCase() === key[0] && key[0] !== "_"));
+ const noviceKeys = Array.from(keys).filter(
+ key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
+ );
return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
}
@@ -907,81 +1073,77 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
}
- }
+ };
@action
onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
this._currentKey = newValue;
- }
+ };
getSuggestionValue = (suggestion: string) => suggestion;
renderSuggestion = (suggestion: string) => {
return <p>{suggestion}</p>;
- }
+ };
onSuggestionFetch = async ({ value }: { value: string }) => {
const sugg = await this.getKeySuggestions(value);
runInAction(() => {
this.suggestions = sugg;
});
- }
+ };
@action
onSuggestionClear = () => {
this.suggestions = [];
- }
+ };
@action
setValue = (value: string) => {
this.document._pivotField = value;
return true;
- }
+ };
@action toggleSort = () => {
- this.document._columnsSort =
- this.document._columnsSort === "descending" ? "ascending" :
- this.document._columnsSort === "ascending" ? undefined : "descending";
- }
- @action resetValue = () => { this._currentKey = this.pivotField; };
+ this.document._columnsSort = this.document._columnsSort === 'descending' ? 'ascending' : this.document._columnsSort === 'ascending' ? undefined : 'descending';
+ };
+ @action resetValue = () => {
+ this._currentKey = this.pivotField;
+ };
render() {
const doctype = this.props.docView.Document.type;
- const isPres: boolean = (doctype === DocumentType.PRES);
- return (
- isPres ? (null) : <div className="collectionStackingViewChrome-cont">
+ const isPres: boolean = doctype === DocumentType.PRES;
+ return isPres ? null : (
+ <div className="collectionStackingViewChrome-cont">
<div className="collectionStackingViewChrome-pivotField-cont">
- <div className="collectionStackingViewChrome-pivotField-label">
- GROUP BY:
- </div>
- <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+ <div className="collectionStackingViewChrome-pivotField-label">GROUP BY:</div>
+ <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? '180' : '0'}deg)` }}>
<FontAwesomeIcon icon="caret-up" size="2x" color="white" />
</div>
<div className="collectionStackingViewChrome-pivotField">
<EditableView
GetValue={() => this.pivotField}
- autosuggestProps={
- {
- resetValue: this.resetValue,
- value: this._currentKey,
- onChange: this.onKeyChange,
- autosuggestProps: {
- inputProps:
- {
- value: this._currentKey,
- onChange: this.onKeyChange
- },
- getSuggestionValue: this.getSuggestionValue,
- suggestions: this.suggestions,
- alwaysRenderSuggestions: true,
- renderSuggestion: this.renderSuggestion,
- onSuggestionsFetchRequested: this.onSuggestionFetch,
- onSuggestionsClearRequested: this.onSuggestionClear
- }
- }}
+ autosuggestProps={{
+ resetValue: this.resetValue,
+ value: this._currentKey,
+ onChange: this.onKeyChange,
+ autosuggestProps: {
+ inputProps: {
+ value: this._currentKey,
+ onChange: this.onKeyChange,
+ },
+ getSuggestionValue: this.getSuggestionValue,
+ suggestions: this.suggestions,
+ alwaysRenderSuggestions: true,
+ renderSuggestion: this.renderSuggestion,
+ onSuggestionsFetchRequested: this.onSuggestionFetch,
+ onSuggestionsClearRequested: this.onSuggestionClear,
+ },
+ }}
oneLine
SetValue={this.setValue}
- contents={this.pivotField ? this.pivotField : "N/A"}
+ contents={this.pivotField ? this.pivotField : 'N/A'}
/>
</div>
</div>
@@ -992,13 +1154,19 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@observer
export class CollectionNoteTakingViewChrome extends React.Component<CollectionViewMenuProps> {
- @observable private _currentKey: string = "";
+ @observable private _currentKey: string = '';
@observable private suggestions: string[] = [];
- get document() { return this.props.docView.props.Document; }
+ get document() {
+ return this.props.docView.props.Document;
+ }
- @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
- @computed get pivotField() { return StrCast(this.document._pivotField); }
+ @computed private get descending() {
+ return StrCast(this.document._columnsSort) === 'descending';
+ }
+ @computed get pivotField() {
+ return StrCast(this.document._pivotField);
+ }
getKeySuggestions = async (value: string): Promise<string[]> => {
const val = value.toLowerCase();
@@ -1006,16 +1174,14 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
if (Doc.UserDoc().noviceMode) {
if (docs instanceof Doc) {
- const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
- key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
- (key[0].toUpperCase() === key[0] && key[0] !== "_"));
+ const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
} else {
const keys = new Set<string>();
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
- key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
- (key[0]?.toUpperCase() === key[0] && key[0] !== "_"));
+ const noviceKeys = Array.from(keys).filter(
+ key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
+ );
return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
}
}
@@ -1027,81 +1193,77 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
}
- }
+ };
@action
onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
this._currentKey = newValue;
- }
+ };
getSuggestionValue = (suggestion: string) => suggestion;
renderSuggestion = (suggestion: string) => {
return <p>{suggestion}</p>;
- }
+ };
onSuggestionFetch = async ({ value }: { value: string }) => {
const sugg = await this.getKeySuggestions(value);
runInAction(() => {
this.suggestions = sugg;
});
- }
+ };
@action
onSuggestionClear = () => {
this.suggestions = [];
- }
+ };
@action
setValue = (value: string) => {
this.document._pivotField = value;
return true;
- }
+ };
@action toggleSort = () => {
- this.document._columnsSort =
- this.document._columnsSort === "descending" ? "ascending" :
- this.document._columnsSort === "ascending" ? undefined : "descending";
- }
- @action resetValue = () => { this._currentKey = this.pivotField; };
+ this.document._columnsSort = this.document._columnsSort === 'descending' ? 'ascending' : this.document._columnsSort === 'ascending' ? undefined : 'descending';
+ };
+ @action resetValue = () => {
+ this._currentKey = this.pivotField;
+ };
render() {
const doctype = this.props.docView.Document.type;
- const isPres: boolean = (doctype === DocumentType.PRES);
- return (
- isPres ? (null) : <div className="collectionStackingViewChrome-cont">
+ const isPres: boolean = doctype === DocumentType.PRES;
+ return isPres ? null : (
+ <div className="collectionStackingViewChrome-cont">
<div className="collectionStackingViewChrome-pivotField-cont">
- <div className="collectionStackingViewChrome-pivotField-label">
- GROUP BY:
- </div>
- <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+ <div className="collectionStackingViewChrome-pivotField-label">GROUP BY:</div>
+ <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? '180' : '0'}deg)` }}>
<FontAwesomeIcon icon="caret-up" size="2x" color="white" />
</div>
<div className="collectionStackingViewChrome-pivotField">
<EditableView
GetValue={() => this.pivotField}
- autosuggestProps={
- {
- resetValue: this.resetValue,
- value: this._currentKey,
- onChange: this.onKeyChange,
- autosuggestProps: {
- inputProps:
- {
- value: this._currentKey,
- onChange: this.onKeyChange
- },
- getSuggestionValue: this.getSuggestionValue,
- suggestions: this.suggestions,
- alwaysRenderSuggestions: true,
- renderSuggestion: this.renderSuggestion,
- onSuggestionsFetchRequested: this.onSuggestionFetch,
- onSuggestionsClearRequested: this.onSuggestionClear
- }
- }}
+ autosuggestProps={{
+ resetValue: this.resetValue,
+ value: this._currentKey,
+ onChange: this.onKeyChange,
+ autosuggestProps: {
+ inputProps: {
+ value: this._currentKey,
+ onChange: this.onKeyChange,
+ },
+ getSuggestionValue: this.getSuggestionValue,
+ suggestions: this.suggestions,
+ alwaysRenderSuggestions: true,
+ renderSuggestion: this.renderSuggestion,
+ onSuggestionsFetchRequested: this.onSuggestionFetch,
+ onSuggestionsClearRequested: this.onSuggestionClear,
+ },
+ }}
oneLine
SetValue={this.setValue}
- contents={this.pivotField ? this.pivotField : "N/A"}
+ contents={this.pivotField ? this.pivotField : 'N/A'}
/>
</div>
</div>
@@ -1110,11 +1272,12 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
}
}
-
@observer
export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> {
// private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
- get document() { return this.props.docView.props.Document; }
+ get document() {
+ return this.props.docView.props.Document;
+ }
@undoBatch
togglePreview = () => {
@@ -1124,12 +1287,12 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewMe
const previewWidth = NumCast(this.document.schemaPreviewWidth);
const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
this.document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
- }
+ };
@undoBatch
@action
toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []);
+ const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []);
if (textwrappedRows.length) {
this.document.textwrappedSchemaRows = new List<string>([]);
} else {
@@ -1137,56 +1300,52 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewMe
const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
this.document.textwrappedSchemaRows = new List<string>(allRows);
}
- }
-
+ };
render() {
const previewWidth = NumCast(this.document.schemaPreviewWidth);
- const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []).length > 0;
return (
<div className="collectionSchemaViewChrome-cont">
<div className="collectionSchemaViewChrome-toggle">
<div className="collectionSchemaViewChrome-label">Show Preview: </div>
<div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
- <div className={"collectionSchemaViewChrome-togglerButton" + (previewWidth !== 0 ? " on" : " off")}>
- {previewWidth !== 0 ? "on" : "off"}
- </div>
+ <div className={'collectionSchemaViewChrome-togglerButton' + (previewWidth !== 0 ? ' on' : ' off')}>{previewWidth !== 0 ? 'on' : 'off'}</div>
</div>
</div>
- </div >
+ </div>
);
}
}
@observer
export class CollectionTreeViewChrome extends React.Component<CollectionViewMenuProps> {
-
- get document() { return this.props.docView.props.Document; }
+ get document() {
+ return this.props.docView.props.Document;
+ }
get sortAscending() {
- return this.document[this.props.fieldKey + "-sortAscending"];
+ return this.document[this.props.fieldKey + '-sortAscending'];
}
set sortAscending(value) {
- this.document[this.props.fieldKey + "-sortAscending"] = value;
+ this.document[this.props.fieldKey + '-sortAscending'] = value;
}
@computed private get ascending() {
- return Cast(this.sortAscending, "boolean", null);
+ return Cast(this.sortAscending, 'boolean', null);
}
@action toggleSort = () => {
if (this.sortAscending) this.sortAscending = undefined;
else if (this.sortAscending === undefined) this.sortAscending = false;
else this.sortAscending = true;
- }
+ };
render() {
return (
<div className="collectionTreeViewChrome-cont">
<button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}>
- <div className="collectionTreeViewChrome-sortLabel">
- Sort
- </div>
- <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? "90" : this.ascending ? "180" : "0"}deg)` }}>
+ <div className="collectionTreeViewChrome-sortLabel">Sort</div>
+ <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? '90' : this.ascending ? '180' : '0'}deg)` }}>
<FontAwesomeIcon icon="caret-up" size="2x" color="white" />
</div>
</button>
@@ -1195,10 +1354,12 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewMenu
}
}
-// Enter scroll speed for 3D Carousel
+// Enter scroll speed for 3D Carousel
@observer
export class Collection3DCarouselViewChrome extends React.Component<CollectionViewMenuProps> {
- get document() { return this.props.docView.props.Document; }
+ get document() {
+ return this.props.docView.props.Document;
+ }
@computed get scrollSpeed() {
return this.document._autoScrollSpeed;
}
@@ -1211,22 +1372,16 @@ export class Collection3DCarouselViewChrome extends React.Component<CollectionVi
return true;
}
return false;
- }
+ };
render() {
return (
<div className="collection3DCarouselViewChrome-cont">
<div className="collection3DCarouselViewChrome-scrollSpeed-cont">
- {FormattedTextBox.Focused ? <RichTextMenu /> : (null)}
- <div className="collectionStackingViewChrome-scrollSpeed-label">
- AUTOSCROLL SPEED:
- </div>
+ {FormattedTextBox.Focused ? <RichTextMenu /> : null}
+ <div className="collectionStackingViewChrome-scrollSpeed-label">AUTOSCROLL SPEED:</div>
<div className="collection3DCarouselViewChrome-scrollSpeed">
- <EditableView
- GetValue={() => StrCast(this.scrollSpeed)}
- oneLine
- SetValue={this.setValue}
- contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
+ <EditableView GetValue={() => StrCast(this.scrollSpeed)} oneLine SetValue={this.setValue} contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
</div>
</div>
</div>
@@ -1239,21 +1394,21 @@ export class Collection3DCarouselViewChrome extends React.Component<CollectionVi
*/
@observer
export class CollectionGridViewChrome extends React.Component<CollectionViewMenuProps> {
-
private clicked: boolean = false;
private entered: boolean = false;
private decrementLimitReached: boolean = false;
@observable private resize = false;
private resizeListenerDisposer: Opt<Lambda>;
- get document() { return this.props.docView.props.Document; }
+ get document() {
+ return this.props.docView.props.Document;
+ }
componentDidMount() {
-
- runInAction(() => this.resize = this.props.docView.props.PanelWidth() < 700);
+ runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
// listener to reduce text on chrome resize (panel resize)
this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
- runInAction(() => this.resize = newValue < 700);
+ runInAction(() => (this.resize = newValue < 700));
});
}
@@ -1261,14 +1416,16 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
this.resizeListenerDisposer?.();
}
- get numCols() { return NumCast(this.document.gridNumCols, 10); }
+ get numCols() {
+ return NumCast(this.document.gridNumCols, 10);
+ }
/**
- * Sets the value of `numCols` on the grid's Document to the value entered.
- */
+ * Sets the value of `numCols` on the grid's Document to the value entered.
+ */
onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.currentTarget.valueAsNumber > 0) undoBatch(() => this.document.gridNumCols = e.currentTarget.valueAsNumber)();
- }
+ if (e.currentTarget.valueAsNumber > 0) undoBatch(() => (this.document.gridNumCols = e.currentTarget.valueAsNumber))();
+ };
/**
* Sets the value of `rowHeight` on the grid's Document to the value entered.
@@ -1288,7 +1445,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
@undoBatch
toggleFlex = () => {
this.document.gridFlex = !BoolCast(this.document.gridFlex, true);
- }
+ };
/**
* Increments the value of numCols on button click
@@ -1296,9 +1453,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
onIncrementButtonClick = () => {
this.clicked = true;
this.entered && (this.document.gridNumCols as number)--;
- undoBatch(() => this.document.gridNumCols = this.numCols + 1)();
+ undoBatch(() => (this.document.gridNumCols = this.numCols + 1))();
this.entered = false;
- }
+ };
/**
* Decrements the value of numCols on button click
@@ -1307,11 +1464,11 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
this.clicked = true;
if (this.numCols > 1 && !this.decrementLimitReached) {
this.entered && (this.document.gridNumCols as number)++;
- undoBatch(() => this.document.gridNumCols = this.numCols - 1)();
+ undoBatch(() => (this.document.gridNumCols = this.numCols - 1))();
if (this.numCols === 1) this.decrementLimitReached = true;
}
this.entered = false;
- }
+ };
/**
* Increments the value of numCols on button hover
@@ -1323,7 +1480,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
}
this.decrementLimitReached = false;
this.clicked = false;
- }
+ };
/**
* Decrements the value of numCols on button hover
@@ -1333,21 +1490,20 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
if (!this.clicked) {
if (this.numCols > 1) {
this.document.gridNumCols = this.numCols - 1;
- }
- else {
+ } else {
this.decrementLimitReached = true;
}
}
this.clicked = false;
- }
+ };
/**
* Toggles the value of preventCollision
*/
toggleCollisions = () => {
this.document.gridPreventCollision = !this.document.gridPreventCollision;
- }
+ };
/**
* Changes the value of the compactType
@@ -1355,16 +1511,26 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
// need to change startCompaction so that this operation will be undoable.
this.document.gridStartCompaction = e.target.selectedOptions[0].value;
- }
+ };
render() {
return (
- <div className="collectionGridViewChrome-cont" >
- <span className="grid-control" style={{ width: this.resize ? "25%" : "30%" }}>
+ <div className="collectionGridViewChrome-cont">
+ <span className="grid-control" style={{ width: this.resize ? '25%' : '30%' }}>
<span className="grid-icon">
<FontAwesomeIcon icon="columns" size="1x" />
</span>
- <input className="collectionGridViewChrome-entryBox" type="number" value={this.numCols} onChange={this.onNumColsChange} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input
+ className="collectionGridViewChrome-entryBox"
+ type="number"
+ value={this.numCols}
+ onChange={this.onNumColsChange}
+ onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
+ e.stopPropagation();
+ e.preventDefault();
+ e.currentTarget.focus();
+ }}
+ />
<input className="collectionGridViewChrome-columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
<input className="collectionGridViewChrome-columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
</span>
@@ -1374,36 +1540,30 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
</span>
<input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
</span> */}
- <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+ <span className="grid-control" style={{ width: this.resize ? '12%' : '20%' }}>
<input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} />
- <label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
+ <label className="flexLabel">{this.resize ? 'Coll' : 'Collisions'}</label>
</span>
- <select className="collectionGridViewChrome-viewPicker"
+ <select
+ className="collectionGridViewChrome-viewPicker"
style={{ marginRight: 5 }}
onPointerDown={stopPropagation}
onChange={this.changeCompactType}
value={StrCast(this.document.gridStartCompaction, StrCast(this.document.gridCompaction))}>
- {["vertical", "horizontal", "none"].map(type =>
- <option className="collectionGridViewChrome-viewOption"
- onPointerDown={stopPropagation}
- value={type}>
- {this.resize ? type[0].toUpperCase() + type.substring(1) : "Compact: " + type}
+ {['vertical', 'horizontal', 'none'].map(type => (
+ <option className="collectionGridViewChrome-viewOption" onPointerDown={stopPropagation} value={type}>
+ {this.resize ? type[0].toUpperCase() + type.substring(1) : 'Compact: ' + type}
</option>
- )}
+ ))}
</select>
- <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
- <input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
- checked={BoolCast(this.document.gridFlex, true)} />
- <label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
+ <span className="grid-control" style={{ width: this.resize ? '12%' : '20%' }}>
+ <input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex} checked={BoolCast(this.document.gridFlex, true)} />
+ <label className="flexLabel">{this.resize ? 'Flex' : 'Flexible'}</label>
</span>
- <button onClick={() => this.document.gridResetLayout = true}>
- {!this.resize ? "Reset" :
- <FontAwesomeIcon icon="redo-alt" size="1x" />}
- </button>
-
+ <button onClick={() => (this.document.gridResetLayout = true)}>{!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}</button>
</div>
);
}
@@ -1411,7 +1571,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
const dataField = doc[Doc.LayoutFieldKey(doc)];
const childDocs = DocListCast(dataField);
- const currentFrame = Cast(doc._currentFrame, "number", null);
+ const currentFrame = Cast(doc._currentFrame, 'number', null);
if (currentFrame === undefined) {
doc._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
@@ -1419,4 +1579,3 @@ ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
});
-
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index a878033ed..fe98f307e 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -1,50 +1,50 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
.collectionNoteTakingView-DocumentButtons {
- display: flex;
- justify-content: space-between;
- margin: auto;
+ display: flex;
+ justify-content: space-between;
+ margin: auto;
}
.collectionNoteTakingView-addDocumentButton {
- display: flex;
- overflow: hidden;
- margin: auto;
- width: 100%;
- overflow: ellipses;
-
- .editableView-container-editing-oneLine,
- .editableView-container-editing {
- color: grey;
- padding: 10px;
- width: 100%;
- }
-
- .editableView-input:hover,
- .editableView-container-editing:hover,
- .editableView-container-editing-oneLine:hover {
- cursor: text
- }
-
- .editableView-input {
- outline-color: black;
- letter-spacing: 2px;
- color: grey;
- border: 0px;
- padding: 12px 10px 11px 10px;
- }
-
- font-size: 75%;
- letter-spacing: 2px;
- cursor: pointer;
-
- .editableView-input {
- outline-color: black;
- letter-spacing: 2px;
- color: grey;
- border: 0px;
- padding: 12px 10px 11px 10px;
- }
+ display: flex;
+ overflow: hidden;
+ margin: auto;
+ width: 100%;
+ overflow: ellipses;
+
+ .editableView-container-editing-oneLine,
+ .editableView-container-editing {
+ color: grey;
+ padding: 10px;
+ width: 100%;
+ }
+
+ .editableView-input:hover,
+ .editableView-container-editing:hover,
+ .editableView-container-editing-oneLine:hover {
+ cursor: text;
+ }
+
+ .editableView-input {
+ outline-color: black;
+ letter-spacing: 2px;
+ color: grey;
+ border: 0px;
+ padding: 12px 10px 11px 10px;
+ }
+
+ font-size: 75%;
+ letter-spacing: 2px;
+ cursor: pointer;
+
+ .editableView-input {
+ outline-color: black;
+ letter-spacing: 2px;
+ color: grey;
+ border: 0px;
+ padding: 12px 10px 11px 10px;
+ }
}
.collectionNoteTakingView {
@@ -83,21 +83,13 @@
overflow-y: auto;
overflow-x: hidden;
flex-wrap: wrap;
- transition: top .5s;
+ transition: top 0.5s;
- >div {
+ > div {
position: relative;
display: block;
}
- .collectionNoteTakingViewFieldColumn {
- height: max-content;
- }
-
- .collectionNoteTakingViewFieldColumnDragging {
- height: 100%;
- }
-
.collectionSchemaView-previewDoc {
height: 100%;
position: absolute;
@@ -144,33 +136,33 @@
// Documents in NoteTaking view
.collectionNoteTakingView-columnDoc {
display: flex;
- // margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change
+ // margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change
position: relative;
&:hover {
- .hoverButtons{
- opacity: 1;
- }
+ .hoverButtons {
+ opacity: 1;
+ }
}
.hoverButtons {
- display: flex;
- opacity: 0;
- position: absolute;
- height: 100%;
- left: -35px;
- justify-content: center;
- align-items: center;
- padding: 0px 10px;
-
- .buttonWrapper {
- padding: 3px;
- border-radius: 3px;
-
- &:hover {
- background: rgba(0, 0, 0, 0.26);
+ display: flex;
+ opacity: 0;
+ position: absolute;
+ height: 100%;
+ left: -35px;
+ justify-content: center;
+ align-items: center;
+ padding: 0px 10px;
+
+ .buttonWrapper {
+ padding: 3px;
+ border-radius: 3px;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.26);
+ }
}
- }
}
}
@@ -200,7 +192,7 @@
span::before,
span::after {
- content: "";
+ content: '';
width: 50%;
border-top: dashed gray 1px;
position: relative;
@@ -241,7 +233,7 @@
.editableView-input:hover,
.editableView-container-editing:hover,
.editableView-container-editing-oneLine:hover {
- cursor: text
+ cursor: text;
}
.collectionNoteTakingView-sectionHeader-subCont {
@@ -287,7 +279,7 @@
height: 100%;
display: none;
- [class*="css"] {
+ [class*='css'] {
max-width: 102px;
}
@@ -329,7 +321,7 @@
height: 100%;
display: none;
- [class*="css"] {
+ [class*='css'] {
max-width: 102px;
}
@@ -338,7 +330,6 @@
}
.collectionNoteTakingView-optionPicker {
-
.optionOptions {
display: inline;
}
@@ -398,7 +389,7 @@
.editableView-input:hover,
.editableView-container-editing:hover,
.editableView-container-editing-oneLine:hover {
- cursor: text
+ cursor: text;
}
.editableView-input {
@@ -451,7 +442,7 @@
top: 4px;
border-radius: 50% 50%;
background-color: #fff;
- content: " ";
+ content: ' ';
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.26);
-webkit-transform: scale(1);
@@ -481,7 +472,6 @@
}
@media only screen and (max-device-width: 480px) {
-
.collectionNoteTakingView .collectionNoteTakingView-columnDragger,
.collectionNoteTakingView-columnDragger {
width: 0.1;
@@ -489,4 +479,4 @@
opacity: 0;
font-size: 0;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 3e2b8b762..96db23142 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,35 +1,34 @@
-import React = require("react");
-import { CursorProperty } from "csstype";
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Copy, Id, ToScriptString, ToString } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from "../../util/DragManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { LightboxView } from "../LightboxView";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView";
-import { StyleProp } from "../StyleProvider";
-import "./CollectionNoteTakingView.scss";
-import CollectionNoteTakingViewDivider from "./CollectionNoteTakingViewDivider";
-import { CollectionNoteTakingViewColumn } from "./CollectionNoteTakingViewColumn";
-import { CollectionSubView } from "./CollectionSubView";
-import { CollectionViewType } from "./CollectionView";
-import { ObjectField } from "../../../fields/ObjectField";
-import { faThumbsDown } from "@fortawesome/free-solid-svg-icons";
-const _global = (window /* browser */ || global /* node */) as any;
+import React = require('react');
+import { CursorProperty } from 'csstype';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { DataSym, Doc, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DragManager, dropActionType } from '../../util/DragManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { LightboxView } from '../LightboxView';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { StyleProp } from '../StyleProvider';
+import './CollectionNoteTakingView.scss';
+import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
+import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
+import { CollectionSubView } from './CollectionSubView';
+const _global = (window /* browser */ || global) /* node */ as any;
export type collectionNoteTakingViewProps = {
chromeHidden?: boolean;
@@ -38,46 +37,79 @@ export type collectionNoteTakingViewProps = {
NativeHeight?: () => number;
};
-//TODO: somehow need to update the mapping and then have everything else rerender. Maybe with a refresh boolean like
+//TODO: somehow need to update the mapping and then have everything else rerender. Maybe with a refresh boolean like
// in Hypermedia?
@observer
export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() {
- _pivotFieldDisposer?: IReactionDisposer;
- _autoHeightDisposer?: IReactionDisposer;
+ _disposers: { [key: string]: IReactionDisposer } = {};
_masonryGridRef: HTMLDivElement | null = null;
- _draggerRef = React.createRef<HTMLDivElement>();
- // _docXfs: { height: () => number, width: () => number, noteTakingDocTransform: () => Transform }[] = [];
- // @observable _docsByColumnHeader = new Map<string, Doc[]>();
- //TODO: need to make sure that we save the mapping
+ _draggerRef = React.createRef<HTMLDivElement>(); // change to relative widths for deleting. change storage from columnStartXCoords to columnHeaders (schemaHeaderFields has a widgth alrady)
+ @observable columnStartXCoords: number[] = []; // columnHeaders -- SchemaHeaderField -- widht
@observable docsDraggedRowCol: number[] = [];
- @observable _cursor: CursorProperty = "grab";
+ @observable _cursor: CursorProperty = 'grab';
@observable _scroll = 0; // used to force the document decoration to update when scrolling
- @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); }
- @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); }
- @computed get pivotField() { return "Col" }
- @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); }
- @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); }
- @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
- @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); }
- @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
- @computed get numGroupColumns() { return this.columnHeaders.length; }
- @observable columnStartXCoords: number[] = []
- @computed get PanelWidth() {return this.props.PanelWidth()}
- @computed get maxColWdith() {return this.props.PanelWidth() - 2 * this.xMargin;}
-
- // If the user has not yet created any docs (in another view), this will create a single column. Otherwise,
- // it will adjust according to the
+ @computed get chromeHidden() {
+ return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
+ }
+ @computed get columnHeaders() {
+ const columnHeaders = Array.from(Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null));
+ const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders.find(sh => sh.heading === 'unset'));
+
+ // @#Oberable draggedColIndex = ...
+ //@observable cloneDivXYcoords
+ // @observable clonedDiv...
+
+ // render() {
+ // { this.clonedDiv ? <div clone styule={{transform: clonedDivXYCoords}} : null}
+ // }
+
+ // in NoteatakinView Column code, add a poinerDown handler that calls setupMoveUpEvents() which will clone the column div
+ // and re-render it under the cursor during move events.
+ // that move move event will update 2 observales -- the draggedColIndex up above, and the location of the clonedDiv so that the render in this view will know where to render the cloned div
+ // add observable variable that tells drag column to rnder in a different location than where the schemaHeaderFiel sa y ot.
+ // if (col 1 is where col 3) {
+ // return 3 2 1 4 56
+ // }
+ if (needsUnsetCategory) {
+ columnHeaders.push(new SchemaHeaderField('unset'));
+ }
+ return columnHeaders;
+ }
+ @computed get notetakingCategoryField() {
+ return 'NotetakingCategory';
+ }
+ @computed get filteredChildren() {
+ return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout);
+ }
+ @computed get headerMargin() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
+ }
+ @computed get xMargin() {
+ return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, 0.05 * this.props.PanelWidth()));
+ }
+ @computed get yMargin() {
+ return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5);
+ } // 2 * this.gridGap)); }
+ @computed get gridGap() {
+ return NumCast(this.layoutDoc._gridGap, 10);
+ }
+ @computed get numGroupColumns() {
+ return this.columnHeaders.length;
+ }
+ @computed get PanelWidth() {
+ return this.props.PanelWidth();
+ }
+ @computed get maxColWdith() {
+ return this.props.PanelWidth() - 2 * this.xMargin;
+ }
+
+ // If the user has not yet created any docs (in another view), this will create a single column. Otherwise,
+ // it will adjust according to the
constructor(props: any) {
super(props);
if (this.columnHeaders === undefined) {
- this.layoutDoc._columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]);
- this.columnStartXCoords = [0]
- // add all of the docs that have not been added to a column to this new column
- }
- else {
- const numHeaders = this.columnHeaders.length
- this.resizeColumns(numHeaders)
+ this.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]);
}
}
@@ -89,118 +121,131 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
const style = { width: width(), marginTop: this.gridGap, height: height() };
- return <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style} >
- {this.getDisplayDoc(d, width)}
- </div>;
+ return (
+ <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style}>
+ {this.getDisplayDoc(d, width)}
+ </div>
+ );
});
- }
+ };
// [CAVEATS] (1) keep track of the offsetting
// (2) documentView gets unmounted as you remove it from the list
- get Sections() {
+ @computed get Sections() {
+ TraceMobx();
const columnHeaders = this.columnHeaders;
+ let docs = this.childDocs;
const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
- let docs = this.childDocs
- const rowCol = this.docsDraggedRowCol
-
+ const rowCol = this.docsDraggedRowCol;
+
// filter out the currently dragged docs from the child docs, since we will insert them later
if (rowCol.length && DragManager.docsBeingDragged.length) {
- const docIdsToRemove = new Set()
- DragManager.docsBeingDragged.forEach(d => {
- docIdsToRemove.add(d[Id])
- })
- docs = docs.filter(d => !docIdsToRemove.has(d[Id]))
+ const docIdsToRemove = new Set();
+ DragManager.docsBeingDragged.forEach(d => docIdsToRemove.add(d[Id]));
+ docs = docs.filter(d => !docIdsToRemove.has(d[Id]));
}
// this will sort the docs into the correct columns (minus the ones you're currently dragging)
docs.map(d => {
- if (!d[this.pivotField]) {
- d[this.pivotField] = columnHeaders.length > 0 ? columnHeaders[0].heading : `New Column`
- };
- const sectionValue = d[this.pivotField] as object;
-
- // look for if header exists already
- const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
- if (existingHeader) {
- sections.get(existingHeader)!.push(d);
- }
+ const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`;
+
+ // look for if header exists already
+ const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
+ if (existingHeader) {
+ sections.get(existingHeader)!.push(d);
+ }
});
// now we add back in the docs that we're dragging
if (rowCol.length && DragManager.docsBeingDragged.length) {
- const colHeader = columnHeaders[rowCol[1]]
- // TODO: get the actual offset that occurs if the docs were in that column
- const offset = 0
- sections.get(colHeader)?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged)
+ const colHeader = columnHeaders[rowCol[1]];
+ // TODO: get the actual offset that occurs if the docs were in that column
+ const offset = 0;
+ sections.get(colHeader)?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged);
}
return sections;
}
+ removeDocDragHighlight = () => {
+ setTimeout(
+ action(() => (this.docsDraggedRowCol.length = 0)),
+ 100
+ );
+ };
componentDidMount() {
super.componentDidMount?.();
- // reset section headers when a new filter is inputted
- this._pivotFieldDisposer = reaction(
- () => this.pivotField,
- () => this.layoutDoc._columnHeaders = new List()
+ document.addEventListener('pointerup', this.removeDocDragHighlight, true);
+ this._disposers.autoHeight = reaction(
+ () => this.layoutDoc._autoHeight,
+ autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))))
+ );
+ this._disposers.headers = reaction(
+ () => this.columnHeaders.slice(),
+ headers => this.resizeColumns(headers.length),
+ { fireImmediately: true }
);
-
- this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight,
- autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
- this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))))));
}
componentWillUnmount() {
+ document.removeEventListener('pointerup', this.removeDocDragHighlight, true);
super.componentWillUnmount();
- this._pivotFieldDisposer?.();
- this._autoHeightDisposer?.();
+ Object.keys(this._disposers).forEach(key => this._disposers[key]());
}
@action
moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
- }
+ };
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
- }
+ };
- @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
+ @computed get onChildClickHandler() {
+ return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+ }
+ @computed get onChildDoubleClickHandler() {
+ return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+ }
addDocTab = (doc: Doc, where: string) => {
- if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
return true;
}
return this.props.addDocTab(doc, where);
- }
+ };
scrollToBottom = () => {
smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight);
- }
+ };
// let's dive in and get the actual document we want to drag/move around
focusDocument = (doc: Doc, options?: DocFocusOptions) => {
Doc.BrushDoc(doc);
let focusSpeed = 0;
- const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]);
+ const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
const top = found.getBoundingClientRect().top;
const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
- smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+ smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
}
}
- const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing;
+ const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing);
this.props.focus(this.rootDoc, {
- willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
+ willZoom: options?.willZoom,
+ scale: options?.scale,
+ afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)),
});
- }
+ };
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+ if (property === StyleProp.BoxShadow && doc && DragManager.docsBeingDragged.includes(doc)) {
+ return `#9c9396 ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}`;
+ }
if (property === StyleProp.Opacity && doc) {
if (this.props.childOpacity) {
return this.props.childOpacity();
@@ -210,243 +255,241 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
}
}
return this.props.styleProvider?.(doc, props, property);
- }
+ };
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
// rules for displaying the documents
getDisplayDoc(doc: Doc, width: () => number) {
- const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
let dref: Opt<DocumentView>;
const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
- return <DocumentView ref={r => dref = r || undefined}
- Document={doc}
- DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
- renderDepth={this.props.renderDepth + 1}
- PanelWidth={width}
- PanelHeight={height}
- styleProvider={this.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={this.props.docViewPath}
- fitWidth={this.props.childFitWidth}
- isContentActive={emptyFunction}
- originalBackgroundColor={StrCast(doc.backgroundColor)}
- //TODO: change this from a prop to a parameter passed into a function
- isNoteTakingView={true}
- isDocumentActive={this.isContentActive}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- freezeDimensions={this.props.childFreezeDimensions}
- NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
- NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined}
- dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
- dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
- rootSelected={this.rootSelected}
- showTitle={this.props.childShowTitle}
- dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
- onClick={this.onChildClickHandler}
- onDoubleClick={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={noteTakingDocTransform}
- focus={this.focusDocument}
- docFilters={this.childDocFilters}
- hideDecorationTitle={this.props.childHideDecorationTitle?.()}
- hideResizeHandles={this.props.childHideResizeHandles?.()}
- hideTitle={this.props.childHideTitle?.()}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.addDocTab}
- bringToFront={returnFalse}
- scriptContext={this.props.scriptContext}
- pinToPres={this.props.pinToPres}
- />;
+ return (
+ <DocumentView
+ ref={r => (dref = r || undefined)}
+ Document={doc}
+ DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
+ renderDepth={this.props.renderDepth + 1}
+ PanelWidth={width}
+ PanelHeight={height}
+ styleProvider={this.styleProvider}
+ docViewPath={this.props.docViewPath}
+ fitWidth={this.props.childFitWidth}
+ isContentActive={emptyFunction}
+ onKey={this.onKeyDown}
+ //TODO: change this from a prop to a parameter passed into a function
+ dontHideOnDrag={true}
+ isDocumentActive={this.isContentActive}
+ LayoutTemplate={this.props.childLayoutTemplate}
+ LayoutTemplateString={this.props.childLayoutString}
+ NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+ NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
+ dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined}
+ dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
+ rootSelected={this.rootSelected}
+ showTitle={this.props.childShowTitle}
+ dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+ onClick={this.onChildClickHandler}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ ScreenToLocalTransform={noteTakingDocTransform}
+ focus={this.focusDocument}
+ docFilters={this.childDocFilters}
+ hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+ hideResizeHandles={this.props.childHideResizeHandles?.()}
+ hideTitle={this.props.childHideTitle?.()}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.addDocTab}
+ bringToFront={returnFalse}
+ scriptContext={this.props.scriptContext}
+ pinToPres={this.props.pinToPres}
+ />
+ );
}
// This is used to get the coordinates of a document when we go from a view like freeform to columns
getDocTransform(doc: Doc, dref?: DocumentView) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
- // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
- return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
+ // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
+ return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
}
// how to get the width of a document. Currently returns the width of the column (minus margins)
// if a note doc. Otherwise, returns the normal width (for graphs, images, etc...)
getDocWidth(d: Doc) {
- const heading = d[this.pivotField] as object
- const castedSectionValue = heading.toString()
- const existingHeader = this.columnHeaders.find(sh => sh.heading === (castedSectionValue));
- const colStartXCoords = this.columnStartXCoords
- if (!existingHeader) {
- return 1000
- }
- const index = this.columnHeaders.indexOf(existingHeader)
- const endColValue = index == this.columnHeaders.length - 1 ? this.PanelWidth : this.columnStartXCoords[index+1]
- const maxWidth = endColValue - colStartXCoords[index] - 3 * this.xMargin
- if (d.type === DocumentType.RTF) {
- return maxWidth
- }
- const width = d[WidthSym]()
- return width < maxWidth ? width : maxWidth
+ const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
+ const existingHeader = this.columnHeaders.find(sh => sh.heading === heading);
+ const index = existingHeader ? this.columnHeaders.indexOf(existingHeader) : 0;
+ const endColValue = index === this.columnHeaders.length - 1 || index > this.columnStartXCoords.length - 1 ? this.PanelWidth : this.columnStartXCoords[index + 1];
+ const maxWidth = index > this.columnStartXCoords.length - 1 ? this.PanelWidth : endColValue - this.columnStartXCoords[index] - 3 * this.xMargin;
+ if (d.type === DocumentType.RTF) {
+ return maxWidth;
+ }
+ const width = d[WidthSym]();
+ return width < maxWidth ? width : maxWidth;
}
// how to get the height of a document. Nothing special here.
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc;
- const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1));
+ const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+ const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
if (nw && nh) {
// const colWid = this.columnWidth / this.numGroupColumns;
// const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
- const docWid = this.getDocWidth(d)
- return Math.min(
- maxHeight,
- docWid * nh / nw);
+ const docWid = this.getDocWidth(d);
+ return Math.min(maxHeight, (docWid * nh) / nw);
}
const childHeight = NumCast(childLayoutDoc._height);
- const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
+ const panelHeight = childLayoutDoc._fitWidth || this.props.childFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
return Math.min(childHeight, maxHeight, panelHeight);
}
// called when a column is either added or deleted. This function creates n evenly spaced columns
+ @action
resizeColumns = (n: number) => {
- const totalWidth = this.PanelWidth
- const dividerWidth = 32
- const totaldividerWidth = (n - 1) * dividerWidth
- const colWidth = (totalWidth - totaldividerWidth) / n
- const newColXCoords: number[] = []
- let colStart = 0
- for (let i = 0; i < n; i++) {
- newColXCoords.push(colStart)
- colStart += colWidth + dividerWidth
- }
- this.columnStartXCoords = newColXCoords
- }
+ const totalWidth = this.PanelWidth;
+ const dividerWidth = 32;
+ const totaldividerWidth = (n - 1) * dividerWidth;
+ const colWidth = (totalWidth - totaldividerWidth) / n;
+ const newColXCoords: number[] = [];
+ let colStart = 0;
+ for (let i = 0; i < n; i++) {
+ newColXCoords.push(colStart);
+ colStart += colWidth + dividerWidth;
+ }
+ this.columnStartXCoords = newColXCoords;
+ };
// This function is used to preview where a document will drop in a column once a drag is complete.
@action
- onPointerOver = (e: React.PointerEvent) => {
- if (DragManager.docsBeingDragged.length && this.childDocList) {
- // get the current docs for the column based on the mouse's x coordinate
- // will use again later, which is why we're saving as local
- const xCoord = e.clientX - 2 * this.gridGap
- const colDocs = this.getDocsFromXCoord(xCoord)
- // get the index for where you need to insert the doc you are currently dragging
- const clientY = e.clientY
- let dropInd = -1;
- // unsure whether we still want this dropAfter field
- // let dropAfter = 0;
- // manually set to 140, because not sure how to get exact value
- let pos0 = 140
- colDocs.forEach((doc, i) => {
- const noteTakingDocTransform = () => this.getDocTransform(doc);
- let pos1 = noteTakingDocTransform().inverse().transformPoint(0, this.getDocHeight(doc) + 2 * this.gridGap)[1];
- pos1 += pos0
- // updating drop position based on y coordinates
- const yCoordInBetween = clientY > pos0 && (clientY < pos1 || i == colDocs.length - 1)
- if (yCoordInBetween) {
- dropInd = i;
- // dropAfter = 0;
- if (clientY > (pos0 + pos1) / 2) {
- // dropAfter = 1;
- }
- }
- pos0 = pos1
- })
- // we alter the pivot fields of the docs in case they are moved to a new column.
- const colIndex = this.getColumnFromXCoord(xCoord)
- const colHeader = StrCast(this.columnHeaders[colIndex].heading)
- DragManager.docsBeingDragged.forEach(d => d[this.pivotField] = colHeader)
- // used to notify sections to re-render
- // console.log([dropInd, this.getColumnFromXCoord(xCoord)])
- this.docsDraggedRowCol = [dropInd, this.getColumnFromXCoord(xCoord)]
- }
- }
+ onPointerMove = (force: boolean, ex: number, ey: number) => {
+ if (this.childDocList && (this.childDocList.includes(DragManager.DocDragData?.draggedDocuments.lastElement()!) || force || this.isContentActive())) {
+ // get the current docs for the column based on the mouse's x coordinate
+ // will use again later, which is why we're saving as local
+ const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap;
+ const colDocs = this.getDocsFromXCoord(xCoord);
+ // get the index for where you need to insert the doc you are currently dragging
+ const clientY = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[1];
+ let dropInd = -1;
+ let pos0 = (this.refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
+ colDocs.forEach((doc, i) => {
+ let pos1 = this.getDocHeight(doc) + 2 * this.gridGap;
+ pos1 += pos0;
+ // updating drop position based on y coordinates
+ const yCoordInBetween = clientY > pos0 && clientY < pos1;
+ if (yCoordInBetween || (clientY < pos0 && i === 0)) {
+ dropInd = i;
+ } else if (i === colDocs.length - 1 && dropInd === -1) {
+ dropInd = !colDocs.includes(DragManager.docsBeingDragged.lastElement()) ? i + 1 : i;
+ }
+ pos0 = pos1;
+ });
+ // we alter the pivot fields of the docs in case they are moved to a new column.
+ const colIndex = this.getColumnFromXCoord(xCoord);
+ const colHeader = StrCast(this.columnHeaders[colIndex].heading);
+ DragManager.docsBeingDragged.forEach(d => (d[this.notetakingCategoryField] = colHeader));
+ // used to notify sections to re-render
+ this.docsDraggedRowCol.length = 0;
+ this.docsDraggedRowCol.push(dropInd, this.getColumnFromXCoord(xCoord));
+ }
+ };
// returns the column index for a given x-coordinate
getColumnFromXCoord = (xCoord: number): number => {
- const numColumns = this.columnHeaders.length
- const coords = this.columnStartXCoords.slice()
- coords.push(this.PanelWidth)
- let colIndex = 0
+ const numColumns = this.columnHeaders.length;
+ const coords = this.columnStartXCoords.slice();
+ coords.push(this.PanelWidth);
+ let colIndex = 0;
for (let i = 0; i < numColumns; i++) {
if (xCoord > coords[i] && xCoord < coords[i + 1]) {
- colIndex = i
- break
+ colIndex = i;
+ break;
}
}
- return colIndex
- }
+ return colIndex;
+ };
- // returns the docs of a column based on the x-coordinate provided.
+ // returns the docs of a column based on the x-coordinate provided.
getDocsFromXCoord = (xCoord: number): Doc[] => {
- const colIndex = this.getColumnFromXCoord(xCoord)
- const colHeader = StrCast(this.columnHeaders[colIndex].heading)
- // const docs = this.childDocList
- const docs = this.childDocs
- const docsMatchingHeader: Doc[] = []
- if (docs) {
- docs.map(d => {
- if (d instanceof Promise) return;
- const sectionValue = d[this.pivotField] as object;
- if (sectionValue.toString() == colHeader) {
- docsMatchingHeader.push(d)
- }
- })
- }
- return docsMatchingHeader;
- }
+ const colIndex = this.getColumnFromXCoord(xCoord);
+ const colHeader = StrCast(this.columnHeaders[colIndex].heading);
+ // const docs = this.childDocList
+ const docs = this.childDocs;
+ const docsMatchingHeader: Doc[] = [];
+ if (docs) {
+ docs.map(d => {
+ if (d instanceof Promise) return;
+ const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset';
+ if (sectionValue.toString() == colHeader) {
+ docsMatchingHeader.push(d);
+ }
+ });
+ }
+ return docsMatchingHeader;
+ };
+
+ @undoBatch
+ @action
+ onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ const docView = fieldProps.DocumentView?.();
+ if (docView && (e.ctrlKey || docView.rootDoc._singleLine) && ['Enter'].includes(e.key)) {
+ e.stopPropagation?.();
+ const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+ Doc.GetProto(newDoc).text = undefined;
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ return this.addDocument?.(newDoc);
+ }
+ };
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
if (super.onInternalDrop(e, de)) {
- DragManager.docsBeingDragged = []
- // this.docsDraggedRowCol = []
- // filter out the currently dragged docs from the child docs, since we will insert them later
- const rowCol = this.docsDraggedRowCol
- const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
- const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments;
-
- // const docs = this.childDocs
- const docs = this.childDocList
- if (docs && newDocs.length) {
- // remove the dragged documents from the childDocList
- newDocs.filter(d => docs.indexOf(d) !== -1).forEach(d => docs.splice(docs.indexOf(d), 1))
- // if the doc starts a columnm (or the drop index is undefined), we can just push it to the front. Otherwise we need to add it to the column properly
- //TODO: you need to update childDocList instead. It seems that childDocs is a copy of the actual array we want to modify
- if (rowCol[0] <= 0) {
- docs.splice(0, 0, ...newDocs)
- } else {
- const colDocs = this.getDocsFromXCoord(de.x)
- const previousDoc = colDocs[rowCol[0] - 1]
- const previousDocIndex = docs.indexOf(previousDoc)
- console.log(`docs: ${previousDocIndex}`)
- docs.splice(previousDocIndex + 1, 0, ...newDocs)
+ // filter out the currently dragged docs from the child docs, since we will insert them later
+ const rowCol = this.docsDraggedRowCol;
+ const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
+ const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments;
+
+ // const docs = this.childDocs
+ const docs = this.childDocList;
+ if (docs && newDocs.length) {
+ // remove the dragged documents from the childDocList
+ newDocs.filter(d => docs.indexOf(d) !== -1).forEach(d => docs.splice(docs.indexOf(d), 1));
+ // if the doc starts a columnm (or the drop index is undefined), we can just push it to the front. Otherwise we need to add it to the column properly
+ //TODO: you need to update childDocList instead. It seems that childDocs is a copy of the actual array we want to modify
+ if (rowCol[0] <= 0) {
+ docs.splice(0, 0, ...newDocs);
+ } else {
+ const colDocs = this.getDocsFromXCoord(this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]);
+ const previousDoc = colDocs[rowCol[0] - 1];
+ const previousDocIndex = docs.indexOf(previousDoc);
+ docs.splice(previousDocIndex + 1, 0, ...newDocs);
+ }
}
- }
}
} // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging
else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
- const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" });
+ const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
e.stopPropagation();
- }
- else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
+ } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
return false;
- }
+ };
@undoBatch
internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) {
@@ -480,139 +523,123 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
// }
// when dropping outside of the current noteTaking context (like a new tab, freeform view, etc...)
- @undoBatch
- @action
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
- const where = [e.clientX, e.clientY];
- let targInd = -1;
- const docs = this.getDocsFromXCoord(where[0])
- docs.map((d, i) => {
- const pos0 = this.getDocTransform(d).inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
- const pos1 = this.getDocTransform(d).inverse().transformPoint(this.getDocWidth(d), this.getDocHeight(d));
- // const pos0 = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
- // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height());
- if (where[0] > pos0[0] && where[0] < pos1[0] && where[1] > pos0[1] && where[1] < pos1[1]) {
- targInd = i;
- }
- })
- // this._docXfs.map((cd, i) => {
- // const pos = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
- // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height());
- // if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
- // targInd = i;
- // }
- // });
- super.onExternalDrop(e, {}, () => {
- if (targInd !== -1) {
- const newDoc = this.childDocs[this.childDocs.length - 1];
- const docs = this.childDocList;
- if (docs) {
- docs.splice(docs.length - 1, 1);
- docs.splice(targInd, 0, newDoc);
- }
- }
- });
- }
-
- // setDocsForColHeader = (key: string, docs: Doc[]) => {
- // this._docsByColumnHeader = new Map(this._docsByColumnHeader.set(key, docs))
- // }
+ const targInd = this.docsDraggedRowCol?.[0] || 0;
+ const colInd = this.docsDraggedRowCol?.[1] || 0;
+ super.onExternalDrop(
+ e,
+ {},
+ undoBatch(
+ action(docus => {
+ this.onPointerMove(true, e.clientX, e.clientY);
+ docus?.map((doc: Doc) => this.addDocument(doc));
+ const newDoc = this.childDocs.lastElement();
+ const colHeader = StrCast(this.columnHeaders[colInd].heading);
+ newDoc[this.notetakingCategoryField] = colHeader;
+ const docs = this.childDocList;
+ if (docs && targInd !== -1) {
+ docs.splice(docs.length - 1, 1);
+ docs.splice(targInd, 0, newDoc);
+ }
+ this.removeDocDragHighlight();
+ })
+ )
+ );
+ };
headings = () => Array.from(this.Sections);
refList: any[] = [];
+ editableViewProps = () => ({
+ GetValue: () => '',
+ SetValue: this.addGroup,
+ contents: '+ New Column',
+ });
sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
- const type = "number";
- return <CollectionNoteTakingViewColumn
- unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
- observeHeight={ref => {
- if (ref) {
- this.refList.push(ref);
- this.observer = new _global.ResizeObserver(action((entries: any) => {
- if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
- const height = this.headerMargin +
- Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
- Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
- if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- this.props.setHeight(height);
- }
- }
- }));
- this.observer.observe(ref);
- }
- }}
- addDocument={this.addDocument}
- // docsByColumnHeader={this._docsByColumnHeader}
- // setDocsForColHeader={this.setDocsForColHeader}
- chromeHidden={this.chromeHidden}
- columnHeaders={this.columnHeaders}
- Document={this.props.Document}
- DataDoc={this.props.DataDoc}
- resizeColumns={this.resizeColumns.bind(this)}
- renderChildren={this.children}
- numGroupColumns={this.numGroupColumns}
- gridGap={this.gridGap}
- pivotField={this.pivotField}
- columnStartXCoords={this.columnStartXCoords}
- maxColWidth={this.maxColWdith}
- PanelWidth={this.PanelWidth}
- key={heading?.heading ?? ""}
- headings={this.headings}
- heading={heading?.heading ?? ""}
- headingObject={heading}
- docList={docList}
- yMargin={this.yMargin}
- type={type}
- createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.props.ScreenToLocalTransform}
- editableViewProps={{
- GetValue: () => "",
- SetValue: this.addGroup,
- contents: "+ New Column"
- }}
- />;
- }
+ const type = 'number';
+ return (
+ <CollectionNoteTakingViewColumn
+ unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
+ observeHeight={ref => {
+ if (ref) {
+ this.refList.push(ref);
+ this.observer = new _global.ResizeObserver(
+ action((entries: any) => {
+ if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
+ if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
+ this.props.setHeight?.(height);
+ }
+ }
+ })
+ );
+ this.observer.observe(ref);
+ }
+ }}
+ addDocument={this.addDocument}
+ // docsByColumnHeader={this._docsByColumnHeader}
+ // setDocsForColHeader={this.setDocsForColHeader}
+ chromeHidden={this.chromeHidden}
+ columnHeaders={this.columnHeaders}
+ Document={this.props.Document}
+ DataDoc={this.props.DataDoc}
+ resizeColumns={this.resizeColumns}
+ renderChildren={this.children}
+ numGroupColumns={this.numGroupColumns}
+ gridGap={this.gridGap}
+ pivotField={this.notetakingCategoryField}
+ columnStartXCoords={this.columnStartXCoords}
+ maxColWidth={this.maxColWdith}
+ PanelWidth={this.PanelWidth}
+ key={heading?.heading ?? ''}
+ headings={this.headings}
+ heading={heading?.heading ?? ''}
+ headingObject={heading}
+ docList={docList}
+ yMargin={this.yMargin}
+ type={type}
+ createDropTarget={this.createDashEventsTarget}
+ screenToLocalTransform={this.props.ScreenToLocalTransform}
+ editableViewProps={this.editableViewProps}
+ />
+ );
+ };
// called when adding a new columnHeader
+ @undoBatch
@action
@undoBatch
addGroup = (value: string) => {
- if (value && this.columnHeaders) {
- const schemaHdrField = new SchemaHeaderField(value);
- this.columnHeaders.push(schemaHdrField);
- DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]);
- this.resizeColumns(this.columnHeaders.length + 1)
- return true;
- }
- return false;
- }
+ const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null);
+ return value && columnHeaders?.push(new SchemaHeaderField(value)) ? true : false;
+ };
sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => {
- const descending = StrCast(this.layoutDoc._columnsSort) === "descending";
+ const descending = StrCast(this.layoutDoc._columnsSort) === 'descending';
const firstEntry = descending ? b : a;
const secondEntry = descending ? a : b;
return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
- }
+ };
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
if (!e.isPropagationStopped()) {
const subItems: ContextMenuProps[] = [];
- subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" });
- subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
+ subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
+ subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+ subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
}
- }
+ };
// used to reset column sizes when using the drag handlers
@action
setColumnStartXCoords = (movementX: number, colIndex: number) => {
- const coords = [...this.columnStartXCoords]
- coords[colIndex] += movementX
- this.columnStartXCoords = coords
- }
+ const coords = [...this.columnStartXCoords];
+ coords[colIndex] += movementX;
+ this.columnStartXCoords = coords;
+ };
@computed get renderedSections() {
TraceMobx();
@@ -622,22 +649,16 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
// sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries;
// }
const entries = Array.from(this.Sections.entries());
- const sections = entries.sort(this.sortFunc)
- const eles: JSX.Element[] = []
+ const sections = entries; //.sort(this.sortFunc);
+ const eles: JSX.Element[] = [];
for (let i = 0; i < sections.length; i++) {
- const col = this.sectionNoteTaking(sections[i][0], sections[i][1])
- eles.push(col)
+ const col = this.sectionNoteTaking(sections[i][0], sections[i][1]);
+ eles.push(col);
if (i < sections.length - 1) {
- eles.push(
- <CollectionNoteTakingViewDivider
- index={i + 1}
- setColumnStartXCoords={this.setColumnStartXCoords.bind(this)}
- xMargin={this.xMargin}
- />
- )
+ eles.push(<CollectionNoteTakingViewDivider key={`divider${i}`} index={i + 1} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
}
}
- return eles
+ return eles;
}
@computed get buttonMenu() {
@@ -646,80 +667,85 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti
if (menuDoc) {
const width: number = NumCast(menuDoc._width, 30);
const height: number = NumCast(menuDoc._height, 30);
- return (<div className="buttonMenu-docBtn"
- style={{ width: width, height: height }}>
- <DocumentView
- Document={menuDoc}
- DataDoc={menuDoc}
- isContentActive={this.props.isContentActive}
- isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={emptyFunction}
- rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={() => 35}
- PanelHeight={() => 35}
- renderDepth={this.props.renderDepth}
- focus={emptyFunction}
- styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={returnEmptyDoclist}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={this.props.docFilters}
- docRangeFilters={this.props.docRangeFilters}
- searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- />
- </div>
+ return (
+ <div className="buttonMenu-docBtn" style={{ width: width, height: height }}>
+ <DocumentView
+ Document={menuDoc}
+ DataDoc={menuDoc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={() => 35}
+ PanelHeight={() => 35}
+ renderDepth={this.props.renderDepth}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
);
}
}
+ @computed get nativeWidth() {
+ return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
+ }
+ @computed get nativeHeight() {
+ return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
+ }
- @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); }
- @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); }
-
- @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }
+ @computed get scaling() {
+ return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight;
+ }
- @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); }
+ @computed get backgroundEvents() {
+ return SnappingManager.GetIsDragging();
+ }
observer: any;
render() {
TraceMobx();
const buttonMenu = this.rootDoc.buttonMenu;
- const noviceExplainer = this.rootDoc.explainer;
+ const noviceExplainer = StrCast(this.rootDoc.explainer);
return (
<>
- {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
- {buttonMenu ? this.buttonMenu : null}
- {Doc.UserDoc().noviceMode && noviceExplainer ?
- <div className="documentExplanation">
- {noviceExplainer}
- </div>
- : null
- }
- </div> : null}
- <div className="collectionNoteTakingView"
+ {buttonMenu || noviceExplainer ? (
+ <div className="documentButtonMenu" key="buttons">
+ {buttonMenu ? this.buttonMenu : null}
+ {Doc.UserDoc().noviceMode && noviceExplainer ? <div className="documentExplanation">{noviceExplainer}</div> : null}
+ </div>
+ ) : null}
+ <div
+ className="collectionNoteTakingView"
ref={this.createRef}
+ key="notes"
style={{
- overflowY: this.props.isContentActive() ? "auto" : "hidden",
+ overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: this.backgroundEvents ? "all" : undefined
+ pointerEvents: this.backgroundEvents ? 'all' : undefined,
}}
- onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
- onPointerOver={this.onPointerOver}
+ onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
+ onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))}
+ onPointerMove={e => e.buttons && this.onPointerMove(false, e.clientX, e.clientY)}
+ onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
- >
+ onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}>
{this.renderedSections}
</div>
</>
-
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 0ee5985bb..4286da2e2 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,27 +1,29 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { RichTextField } from "../../../fields/RichTextField";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { ScriptField } from "../../../fields/ScriptField";
-import { ImageField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnEmptyString, setupMoveUpEvents } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DragManager } from "../../util/DragManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { EditableView } from "../EditableView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
-import "./CollectionNoteTakingView.scss";
-const higflyout = require("@hig/flyout");
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { RichTextField } from '../../../fields/RichTextField';
+import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { ScriptField } from '../../../fields/ScriptField';
+import { ImageField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { EditableView } from '../EditableView';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import './CollectionNoteTakingView.scss';
+import { listSpec } from '../../../fields/Schema';
+import { Cast } from '../../../fields/Types';
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -39,7 +41,7 @@ interface CSVFieldColumnProps {
// columnWidth: number;
numGroupColumns: number;
gridGap: number;
- type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
+ type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
headings: () => object[];
renderChildren: (docs: Doc[]) => JSX.Element[];
addDocument: (doc: Doc | Doc[]) => boolean;
@@ -47,38 +49,39 @@ interface CSVFieldColumnProps {
screenToLocalTransform: () => Transform;
observeHeight: (myref: any) => void;
unobserveHeight: (myref: any) => void;
- editableViewProps: any;
- resizeColumns: (n: number) => void
- columnStartXCoords: number[]
- PanelWidth: number
- maxColWidth: number
+ //setDraggedCol:(clonedDiv:any, header:SchemaHeaderField, xycoors: )
+ editableViewProps: () => any;
+ resizeColumns: (n: number) => void;
+ columnStartXCoords: number[];
+ PanelWidth: number;
+ maxColWidth: number;
// docsByColumnHeader: Map<string, Doc[]>
// setDocsForColHeader: (key: string, docs: Doc[]) => void
}
@observer
export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColumnProps> {
- @observable private _background = "inherit";
+ @observable private _background = 'inherit';
@computed get columnWidth() {
- // base cases
- if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) {
- return this.props.maxColWidth
- }
- const i = this.props.columnHeaders.indexOf(this.props.headingObject)
- if (i < 0) {
- return this.props.maxColWidth
- }
- const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i+1]
- // TODO make the math work here. 35 is half of 70, which is the current width of the divider
- return endColValue - this.props.columnStartXCoords[i] - 30
+ // base cases
+ if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) {
+ return this.props.maxColWidth;
+ }
+ const i = this.props.columnHeaders.indexOf(this.props.headingObject);
+ if (i < 0 || i > this.props.columnStartXCoords.length - 1) {
+ return this.props.maxColWidth;
+ }
+ const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i + 1];
+ // TODO make the math work here. 35 is half of 70, which is the current width of the divider
+ return endColValue - this.props.columnStartXCoords[i] - 30;
}
private dropDisposer?: DragManager.DragDropDisposer;
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
@observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
- @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+ @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb';
_ele: HTMLElement | null = null;
// This is likely similar to what we will be doing. Why do we need to make these refs?
@@ -90,7 +93,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
this.props.observeHeight(ele);
this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
- }
+ };
componentWillUnmount() {
this.props.unobserveHeight(this._ele);
@@ -105,10 +108,10 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
getValue = (value: string): any => {
const parsed = parseInt(value);
if (!isNaN(parsed)) return parsed;
- if (value.toLowerCase().indexOf("true") > -1) return true;
- if (value.toLowerCase().indexOf("false") > -1) return false;
+ if (value.toLowerCase().indexOf('true') > -1) return true;
+ if (value.toLowerCase().indexOf('false') > -1) return false;
return value;
- }
+ };
@action
headingChanged = (value: string, shiftDown?: boolean) => {
@@ -117,7 +120,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this.props.docList.forEach(d => d[this.props.pivotField] = castedValue);
+ this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue));
if (this.props.headingObject) {
this.props.headingObject.setHeading(castedValue.toString());
this._heading = this.props.headingObject.heading;
@@ -125,11 +128,11 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
return true;
}
return false;
- }
+ };
- @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4");
- @action pointerLeave = () => this._background = "inherit";
- textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true);
+ @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
+ @action pointerLeave = () => (this._background = 'inherit');
+ textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
@@ -139,50 +142,21 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
const colValue = this.getValue(this.props.heading);
newDoc[key] = colValue;
FormattedTextBox.SelectOnLoad = newDoc[Id];
- FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " ";
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
return this.props.addDocument?.(newDoc) || false;
- }
+ };
+ @undoBatch
@action
@undoBatch
deleteColumn = () => {
- if (!this.props.columnHeaders) {
- return
- }
- if (this.props.headingObject) {
- const index = this.props.columnHeaders.indexOf(this.props.headingObject);
- const newIndex = index == 0 ? 1 : index - 1
- const newHeader = this.props.columnHeaders[newIndex];
- this.props.docList.forEach(d => d[this.props.pivotField] = newHeader.heading.toString())
- this.props.columnHeaders.splice(index, 1);
- // const newHeaders = this.props.columnHeaders;
- // newHeaders.splice(index, 1);
- // this.props.columnHeaders = newHeaders;
- this.props.resizeColumns(this.props.columnHeaders.length)
+ const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null);
+ if (columnHeaders && this.props.headingObject) {
+ const index = columnHeaders.indexOf(this.props.headingObject);
+ this.props.docList.forEach(d => (d[this.props.pivotField] = 'unset'));
+ columnHeaders.splice(index, 1);
}
- }
-
- headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
-
- //TODO: I think this is where I'm supposed to edit stuff
- startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
- console.log('in startDrag')
- // is MakeAlias a way to make a copy of a doc without rendering it?
- const alias = Doc.MakeAlias(this.props.Document);
- // alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1);
- alias._width = this.columnWidth;
- alias._pivotField = undefined;
- let value = this.getValue(this._heading);
- value = typeof value === "string" ? `"${value}"` : value;
- alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
- if (alias.viewSpecScript) {
- const options = {hideSource: false}
- DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY, options);
- console.log('in startDrag')
- return true;
- }
- return false;
- }
+ };
menuCallback = (x: number, y: number) => {
ContextMenu.Instance.clearItems();
@@ -191,44 +165,62 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
const dataDoc = this.props.DataDoc || this.props.Document;
const pivotValue = this.getValue(this.props.heading);
- DocUtils.addDocumentCreatorMenuItems((doc) => {
- const key = this.props.pivotField;
- doc[key] = this.getValue(this.props.heading);
- FormattedTextBox.SelectOnLoad = doc[Id];
- return this.props.addDocument?.(doc);
- }, this.props.addDocument, x, y, true, this.props.pivotField, pivotValue);
+ DocUtils.addDocumentCreatorMenuItems(
+ doc => {
+ const key = this.props.pivotField;
+ doc[key] = this.getValue(this.props.heading);
+ FormattedTextBox.SelectOnLoad = doc[Id];
+ return this.props.addDocument?.(doc);
+ },
+ this.props.addDocument,
+ x,
+ y,
+ true,
+ this.props.pivotField,
+ pivotValue
+ );
- Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey =>
- docItems.push({
- description: ":" + fieldKey, event: () => {
- const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
- if (created) {
- if (this.props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+ if (created) {
+ if (this.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ }
+ return this.props.addDocument?.(created);
}
- return this.props.addDocument?.(created);
- }
- }, icon: "compress-arrows-alt"
- }));
- Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey =>
- docItems.push({
- description: ":" + fieldKey, event: () => {
- const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
- if (created) {
- const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
- if (container.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, container);
- return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
+ if (created) {
+ const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+ if (container.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, container);
+ return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+ }
+ return this.props.addDocument?.(created) || false;
}
- return this.props.addDocument?.(created) || false;
- }
- }, icon: "compress-arrows-alt"
- }));
- !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
- !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
- ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
- Doc.GetProto(this.props.Document)[name] = "";
- const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
+ !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
+ ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
+ Doc.GetProto(this.props.Document)[name] = '';
+ const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true });
if (created) {
if (this.props.Document.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, this.props.Document);
@@ -237,98 +229,98 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
}
});
ContextMenu.Instance.displayMenu(x, y, undefined, true);
- }
+ };
@computed get innards() {
TraceMobx();
const key = this.props.pivotField;
const heading = this._heading;
const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin;
- const evContents = heading ? heading : "25";
- const headingView = this.props.headingObject ?
- <div key={heading} className="collectionNoteTakingView-sectionHeader" ref={this._headerRef}
+ const evContents = heading ? heading : '25';
+ const headingView = this.props.headingObject ? (
+ <div
+ key={heading}
+ className="collectionNoteTakingView-sectionHeader"
+ ref={this._headerRef}
style={{
marginTop: 2 * this.props.yMargin,
// width: (this.props.columnWidth) /
// ((uniqueHeadings.length) || 1)
- width: this.columnWidth - 20
+ width: this.columnWidth - 20,
}}>
- <div className="collectionNoteTakingView-sectionHeader-subCont" onPointerDown={this.headerDown}
- title={evContents === `No Value` ?
- `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
- style={{ background: evContents !== `No Value` ? this._color : "inherit" }}>
- <EditableView
- GetValue={() => evContents}
- SetValue={this.headingChanged}
- contents={evContents}
- oneLine={true}
- />
+ <div
+ className="collectionNoteTakingView-sectionHeader-subCont"
+ title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
+ style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
+ <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
</div>
- </div> : (null);
+ </div>
+ ) : null;
// const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
const templatecols = `${this.columnWidth}px `;
const type = this.props.Document.type;
- return <>
- {headingView}
- {<div>
- <div key={`${heading}-stack`} className={`collectionNoteTakingView-Nodes`}
- style={{
- padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
- margin: "auto",
- width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
- height: 'max-content',
- position: "relative",
- gridGap: this.props.gridGap,
- gridTemplateColumns: templatecols,
- gridAutoRows: "0px"
- }}>
- {this.props.renderChildren(this.props.docList)}
- </div>
+ return (
+ <>
+ {headingView}
+ {
+ <div style={{ height: '100%' }}>
+ <div
+ key={`${heading}-stack`}
+ className={`collectionNoteTakingView-Nodes`}
+ style={{
+ padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
+ margin: 'auto',
+ width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ height: 'max-content',
+ position: 'relative',
+ gridGap: this.props.gridGap,
+ gridTemplateColumns: templatecols,
+ gridAutoRows: '0px',
+ }}>
+ {this.props.renderChildren(this.props.docList)}
+ </div>
- {!this.props.chromeHidden && type !== DocumentType.PRES ?
- <div className="collectionNoteTakingView-DocumentButtons"
- // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
- style={{ width: this.columnWidth - 20, marginBottom: 10 }}>
- <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
- <EditableView
- GetValue={returnEmptyString}
- SetValue={this.addNewTextDoc}
- textCallback={this.textCallback}
- placeholder={"Type ':' for commands"}
- contents={"+ New Node"}
- menuCallback={this.menuCallback}
- />
- </div>
- <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
- <EditableView {...this.props.editableViewProps} />
- </div>
- {(this.props.columnHeaders?.length && this.props.columnHeaders.length > 1) &&
- <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
- <FontAwesomeIcon icon="trash" size="lg" />
- </button>
- }
- </div>
- : null}
- </div>
- }
- </>;
+ {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+ <div
+ className="collectionNoteTakingView-DocumentButtons"
+ // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
+ style={{ width: this.columnWidth - 20, marginBottom: 10 }}>
+ <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
+ </div>
+ <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
+ <EditableView {...this.props.editableViewProps()} />
+ </div>
+ {this.props.columnHeaders?.length && this.props.columnHeaders.length > 1 && (
+ <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
+ <FontAwesomeIcon icon="trash" size="lg" />
+ </button>
+ )}
+ </div>
+ ) : null}
+ </div>
+ }
+ </>
+ );
}
-
render() {
TraceMobx();
const heading = this._heading;
return (
- <div className={"collectionNoteTakingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
+ <div
+ className={'collectionNoteTakingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
+ key={heading}
style={{
- //TODO: change this so that it's based on the column width
+ //TODO: change this so that it's based on the column width
width: this.columnWidth,
- height: "100%",
- background: this._background
+ background: this._background,
}}
- ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
+ ref={this.createColumnDropRef}
+ onPointerEnter={this.pointerEntered}
+ onPointerLeave={this.pointerLeave}>
{this.innards}
- </div >
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index ed5dc3715..7d31b3193 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,61 +1,63 @@
-import { action, observable } from "mobx";
-import * as React from "react";
+import { action, observable } from 'mobx';
+import * as React from 'react';
interface DividerProps {
- index: number
- xMargin: number
- setColumnStartXCoords: (movementX: number, colIndex: number) => void
+ index: number;
+ xMargin: number;
+ setColumnStartXCoords: (movementX: number, colIndex: number) => void;
}
-export default class Divider extends React.Component<DividerProps>{
- @observable private isHoverActive = false;
- @observable private isResizingActive = false;
-
- @action
- private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
- e.stopPropagation();
- e.preventDefault();
- window.removeEventListener("pointermove", this.onPointerMove);
- window.removeEventListener("pointerup", this.onPointerUp);
- window.addEventListener("pointermove", this.onPointerMove);
- window.addEventListener("pointerup", this.onPointerUp);
- this.isResizingActive = true;
- }
+export class CollectionNoteTakingViewDivider extends React.Component<DividerProps> {
+ @observable private isHoverActive = false;
+ @observable private isResizingActive = false;
- @action
- private onPointerUp = () => {
- this.isResizingActive = false;
- this.isHoverActive = false;
- window.removeEventListener("pointermove", this.onPointerMove);
- window.removeEventListener("pointerup", this.onPointerUp);
- }
+ @action
+ private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => {
+ e.stopPropagation();
+ e.preventDefault();
+ window.removeEventListener('pointermove', this.onPointerMove);
+ window.removeEventListener('pointerup', this.onPointerUp);
+ window.addEventListener('pointermove', this.onPointerMove);
+ window.addEventListener('pointerup', this.onPointerUp);
+ this.isResizingActive = true;
+ };
- @action
- onPointerMove = ({ movementX }: PointerEvent) => {
- this.props.setColumnStartXCoords(movementX, this.props.index)
- }
-
- render() {
- return (
- <div className="columnResizer"
- style={{
- display: "flex",
- alignItems: "center",
- cursor: "col-resize"
- }}
- onPointerEnter={action(() => this.isHoverActive = true)}
- onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}
- >
- <div className="columnResizer-handler" onPointerDown={e => this.registerResizing(e)}
- style={{
- height: "95%",
- width: 12,
- borderRight: "4px solid #282828",
- borderLeft: "4px solid #282828",
- margin: "0px 10px"
- }}
- />
- </div>
- )
- }
-} \ No newline at end of file
+ @action
+ private onPointerUp = () => {
+ this.isResizingActive = false;
+ this.isHoverActive = false;
+ window.removeEventListener('pointermove', this.onPointerMove);
+ window.removeEventListener('pointerup', this.onPointerUp);
+ };
+
+ @action
+ onPointerMove = ({ movementX }: PointerEvent) => {
+ this.props.setColumnStartXCoords(movementX, this.props.index);
+ };
+
+ render() {
+ return (
+ <div
+ className="columnResizer"
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ cursor: 'col-resize',
+ }}
+ onPointerEnter={action(() => (this.isHoverActive = true))}
+ onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}>
+ <div
+ className="columnResizer-handler"
+ onPointerDown={e => this.registerResizing(e)}
+ style={{
+ height: '95%',
+ width: 12,
+ borderRight: '4px solid #282828',
+ borderLeft: '4px solid #282828',
+ margin: '0px 10px',
+ }}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 0a336c544..4489601db 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym } from "../../../fields/Doc";
import { NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnTrue, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { SelectionManager } from "../../util/SelectionManager";
import { SnappingManager } from "../../util/SnappingManager";
@@ -11,6 +11,7 @@ import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormV
import "./CollectionPileView.scss";
import { CollectionSubView } from "./CollectionSubView";
import React = require("react");
+import { ScriptField } from "../../../fields/ScriptField";
@observer
export class CollectionPileView extends CollectionSubView() {
@@ -35,26 +36,33 @@ export class CollectionPileView extends CollectionSubView() {
layoutEngine = () => StrCast(this.Document._pileLayoutEngine);
+ @undoBatch
+ addPileDoc = (doc: Doc | Doc[]) => {
+ (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d));
+ return this.props.addDocument?.(doc) || false;
+ }
+
+ @undoBatch
+ removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
+ (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d)));
+ return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+ }
+
+ toggleIcon = () => {
+ return ScriptField.MakeScript("documentView.iconify()", { documentView: "any" });
+ }
+
// returns the contents of the pileup in a CollectionFreeFormView
@computed get contents() {
const isStarburst = this.layoutEngine() === "starburst";
- const draggingSelf = this.props.isSelected();
return <div className="collectionPileView-innards"
- style={{
- pointerEvents: isStarburst || (SnappingManager.GetIsDragging() && !draggingSelf) ? undefined : "none",
- zIndex: isStarburst && !SnappingManager.GetIsDragging() ? -10 : "auto"
- }} >
+ style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : "none" }} >
<CollectionFreeFormView {...this.props}
layoutEngine={this.layoutEngine}
childDocumentsActive={isStarburst ? returnTrue : undefined}
- addDocument={undoBatch((doc: Doc | Doc[]) => {
- (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d));
- return this.props.addDocument?.(doc) || false;
- })}
- moveDocument={undoBatch((doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
- (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d)));
- return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
- })} />
+ addDocument={this.addPileDoc}
+ childClickScript={this.toggleIcon()}
+ moveDocument={this.removePileDoc} />
</div>;
}
@@ -62,20 +70,17 @@ export class CollectionPileView extends CollectionSubView() {
toggleStarburst = action(() => {
if (this.layoutEngine() === 'starburst') {
const defaultSize = 110;
- this.layoutDoc._overflow = undefined;
- this.childDocs.forEach(d => DocUtils.iconify(d));
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
- DocUtils.pileup(this.childDocs);
+ DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
this.layoutDoc._panX = 0;
this.layoutDoc._panY = -10;
this.props.Document._pileLayoutEngine = 'pass';
} else {
const defaultSize = 25;
- this.layoutDoc._overflow = 'visible';
- !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
+ !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
!this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
if (this.layoutEngine() === 'pass') {
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 59c21210a..6611477e5 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,94 +1,146 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.scss';
-.collectionStackedTimeline {
- position: absolute;
- width: 100%;
- height: 100%;
- z-index: 1000;
- overflow: hidden;
- top: 0px;
-
- .collectionStackedTimeline-trim-shade {
- position: absolute;
+.collectionStackedTimeline-timelineContainer {
height: 100%;
- background-color: $dark-gray;
- opacity: 0.3;
- }
+ overflow-x: auto;
+ overflow-y: hidden;
+ border: none;
+ background-color: $white;
+ border-width: 0 2px 0 2px;
- .collectionStackedTimeline-trim-controls {
- height: 100%;
- position: absolute;
- box-sizing: border-box;
- border: 2px solid $medium-blue;
- display: flex;
- justify-content: space-between;
- max-width: 100%;
-
- .collectionStackedTimeline-trim-handle {
- background-color: $medium-blue;
- height: 100%;
- width: 5px;
- cursor: ew-resize;
+ &:hover {
+ .collectionStackedTimeline-hover {
+ display: block;
+ }
}
- }
+}
+
+.collectionStackedTimeline-timelineContainer:hover + .timeline-hoverUI {
+ display: flex;
+ justify-content: center;
+}
+
+::-webkit-scrollbar {
+ height: 5px;
+}
- .collectionStackedTimeline-selector {
+.collectionStackedTimeline {
position: absolute;
- width: 10px;
- top: 2.5%;
- height: 95%;
- background: $light-blue;
- border-radius: 3px;
- opacity: 0.3;
- z-index: 500;
- border-style: solid;
- border-color: $medium-blue;
- border-width: 1px;
- }
-
- .collectionStackedTimeline-current {
- width: 1px;
+ background: $off-white;
+ z-index: 1000;
height: 100%;
- background-color: $pink;
- position: absolute;
- top: 0px;
- pointer-events: none;
- }
+ overflow: hidden;
- .collectionStackedTimeline-marker-timeline {
- position: absolute;
- top: 2.5%;
- height: 95%;
- border-radius: 4px;
- &:hover {
- opacity: 1;
+ .collectionStackedTimeline-trim-shade {
+ position: absolute;
+ height: 100%;
+ background-color: $dark-gray;
+ opacity: 0.3;
+ top: 0;
}
- .collectionStackedTimeline-left-resizer,
- .collectionStackedTimeline-resizer {
- background: $medium-gray;
- position: absolute;
- top: 0;
- height: 100%;
- width: 10px;
- pointer-events: all;
- cursor: ew-resize;
- z-index: 100;
+ .collectionStackedTimeline-trim-controls {
+ height: 100%;
+ position: absolute;
+ box-sizing: border-box;
+ border: 2px solid $medium-blue;
+ display: flex;
+ justify-content: space-between;
+ max-width: 100%;
+ top: 0;
+ left: 0;
+
+ .collectionStackedTimeline-trim-handle {
+ background-color: $medium-blue;
+ height: 100%;
+ width: 5px;
+ cursor: ew-resize;
+ }
+ }
+
+ .collectionStackedTimeline-selector {
+ position: absolute;
+ width: 10px;
+ top: 2.5%;
+ height: 95%;
+ background: $light-blue;
+ border-radius: 3px;
+ opacity: 0.3;
+ z-index: 500;
+ border-style: solid;
+ border-color: $medium-blue;
+ border-width: 1px;
+ }
+
+ .collectionStackedTimeline-current,
+ .collectionStackedTimeline-hover {
+ width: 1px;
+ height: 100%;
+ position: absolute;
+ top: 0px;
+ pointer-events: none;
+ }
+
+ .collectionStackedTimeline-current {
+ background-color: $pink;
+ }
+
+ .collectionStackedTimeline-hover {
+ display: none;
+ background-color: $medium-blue;
}
- .collectionStackedTimeline-resizer {
- right: 0;
+
+ .collectionStackedTimeline-marker-timeline {
+ position: absolute;
+ top: 2.5%;
+ height: 95%;
+ border-radius: 4px;
+ background: $light-gray;
+ &:hover {
+ opacity: 1;
+ }
+
+ .collectionStackedTimeline-left-resizer,
+ .collectionStackedTimeline-resizer {
+ background: $medium-gray;
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: 10px;
+ pointer-events: all;
+ cursor: ew-resize;
+ z-index: 100;
+ }
+ .collectionStackedTimeline-resizer {
+ right: 0;
+ }
+ .collectionStackedTimeline-left-resizer {
+ left: 0;
+ }
}
- .collectionStackedTimeline-left-resizer {
- left: 0;
+
+ .collectionStackedTimeline-waveform {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ pointer-events: none;
}
- }
+}
- .collectionStackedTimeline-waveform {
+.timeline-hoverUI {
position: absolute;
- width: 100%;
+ z-index: 10000;
+ transform: translate(-50%, 100%);
height: 100%;
- top: 0;
- left: 0;
- pointer-events: none;
- }
+ display: none;
+
+ .hoverTime {
+ position: absolute;
+ color: $dark-gray;
+ transform: translate(0, -100%);
+
+ font-weight: bold;
+ }
}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index e09e9aa35..48e3abbc7 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,53 +1,35 @@
-import React = require("react");
-import {
- action,
- computed,
- IReactionDisposer,
- observable,
- reaction,
- runInAction
-} from "mobx";
-import { observer } from "mobx-react";
-import { computedFn } from "mobx-utils";
-import { Doc, DocListCast } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, NumCast } from "../../../fields/Types";
-import {
- emptyFunction,
- formatTime,
- OmitKeys,
- returnFalse,
- returnOne,
- setupMoveUpEvents,
- StopEvent
-} from "../../../Utils";
-import { Docs } from "../../documents/Documents";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
-import { LinkManager } from "../../util/LinkManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SelectionManager } from "../../util/SelectionManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { AudioWaveform } from "../AudioWaveform";
-import { CollectionSubView } from "../collections/CollectionSubView";
-import { Colors } from "../global/globalEnums";
-import { LightboxView } from "../LightboxView";
-import {
- DocAfterFocusFunc,
- DocFocusFunc,
- DocumentView,
- DocumentViewProps
-} from "../nodes/DocumentView";
-import { LabelBox } from "../nodes/LabelBox";
-import "./CollectionStackedTimeline.scss";
+import React = require('react');
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
+import { Doc, DocListCast, StrListCast } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { Cast, NumCast } from '../../../fields/Types';
+import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DragManager } from '../../util/DragManager';
+import { LinkFollower } from '../../util/LinkFollower';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { AudioWaveform } from '../AudioWaveform';
+import { CollectionSubView } from '../collections/CollectionSubView';
+import { Colors } from '../global/globalEnums';
+import { LightboxView } from '../LightboxView';
+import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { LabelBox } from '../nodes/LabelBox';
+import './CollectionStackedTimeline.scss';
+import { VideoBox } from '../nodes/VideoBox';
+import { ImageField } from '../../../fields/URLField';
export type CollectionStackedTimelineProps = {
- duration: number;
Play: () => void;
Pause: () => void;
playLink: (linkDoc: Doc) => void;
@@ -58,60 +40,73 @@ export type CollectionStackedTimelineProps = {
endTag: string;
mediaPath: string;
dictationKey: string;
- trimming: boolean;
- trimStart: number;
- trimEnd: number;
- trimDuration: number;
- setStartTrim: (newStart: number) => void;
- setEndTrim: (newEnd: number) => void;
+ rawDuration: number;
+ fieldKey: string;
};
+// trimming state: shows full clip, current trim bounds, or not trimming
+export enum TrimScope {
+ All = 2,
+ Clip = 1,
+ None = 0,
+}
+
@observer
export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
- @observable static SelectingRegion: CollectionStackedTimeline | undefined =
- undefined;
+ @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
+ @observable public static CurrentlyPlaying: Doc[];
+
static RangeScript: ScriptField;
static LabelScript: ScriptField;
static RangePlayScript: ScriptField;
static LabelPlayScript: ScriptField;
- private _timeline: HTMLDivElement | null = null;
+ private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
+ private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling
private _markerStart: number = 0;
- @observable _markerEnd: number = 0;
+ @observable _markerEnd: number | undefined;
+ @observable _trimming: number = TrimScope.None;
+ @observable _trimStart: number = 0; // trim controls start pos
+ @observable _trimEnd: number = 0; // trim controls end pos
- get minLength() {
- const rect = this._timeline?.getBoundingClientRect();
- if (rect) {
- return 0.05 * this.duration;
- }
- return 0;
- }
+ @observable _zoomFactor: number = 1;
+ @observable _scroll: number = 0;
+
+ @observable _hoverTime: number = 0;
- get trimStart() {
- return this.props.trimStart;
+ @observable _thumbnail: string | undefined;
+
+ // ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore
+ get minTrimLength() {
+ return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5);
}
- get trimEnd() {
- return this.props.trimEnd;
+ @computed get trimStart() {
+ return this.IsTrimming !== TrimScope.None ? this._trimStart : this.clipStart;
+ }
+ @computed get trimDuration() {
+ return this.trimEnd - this.trimStart;
+ }
+ @computed get trimEnd() {
+ return this.IsTrimming !== TrimScope.None ? this._trimEnd : this.clipEnd;
}
- get duration() {
- return this.props.duration;
+ @computed get clipStart() {
+ return this.IsTrimming === TrimScope.All ? 0 : NumCast(this.layoutDoc.clipStart);
+ }
+ @computed get clipDuration() {
+ return this.clipEnd - this.clipStart;
+ }
+ @computed get clipEnd() {
+ return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration);
}
@computed get currentTime() {
return NumCast(this.layoutDoc._currentTimecode);
}
- @computed get selectionContainer() {
- return CollectionStackedTimeline.SelectingRegion !== this ? null : (
- <div
- className="collectionStackedTimeline-selector"
- style={{
- left: `${((Math.min(this._markerStart, this._markerEnd) - this.trimStart) / this.props.trimDuration) * 100}%`,
- width: `${(Math.abs(this._markerStart - this._markerEnd) / this.props.trimDuration) * 100}%`,
- }}
- />
- );
+
+ @computed get zoomFactor() {
+ return this._zoomFactor;
}
constructor(props: any) {
@@ -121,82 +116,109 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
CollectionStackedTimeline.RangeScript ||
ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
self: Doc.name,
- scriptContext: "any",
- clientX: "number",
+ scriptContext: 'any',
+ clientX: 'number',
})!;
CollectionStackedTimeline.RangePlayScript =
CollectionStackedTimeline.RangePlayScript ||
ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, {
self: Doc.name,
- scriptContext: "any",
- clientX: "number",
+ scriptContext: 'any',
+ clientX: 'number',
})!;
}
componentDidMount() {
- document.addEventListener("keydown", this.keyEvents, true);
+ document.addEventListener('keydown', this.keyEvents, true);
}
+
+ @action
componentWillUnmount() {
- document.removeEventListener("keydown", this.keyEvents, true);
+ document.removeEventListener('keydown', this.keyEvents, true);
if (CollectionStackedTimeline.SelectingRegion === this) {
- runInAction(
- () => (CollectionStackedTimeline.SelectingRegion = undefined)
- );
+ CollectionStackedTimeline.SelectingRegion = undefined;
}
}
- anchorStart = (anchor: Doc) =>
- NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]))
- anchorEnd = (anchor: Doc, val: any = null) => {
- const endVal = NumCast(anchor[this.props.endTag], val);
- return NumCast(
- anchor._timecodeToHide,
- endVal === undefined ? null : endVal
- );
+ public get IsTrimming() {
+ return this._trimming;
}
- toTimeline = (screen_delta: number, width: number) => {
- return Math.max(
- this.trimStart,
- Math.min(this.trimEnd, (screen_delta / width) * this.props.trimDuration + this.trimStart));
+
+ @action
+ public StartTrimming(scope: TrimScope) {
+ this._trimStart = this.clipStart;
+ this._trimEnd = this.clipEnd;
+ this._trimming = scope;
+ }
+ @action
+ public StopTrimming() {
+ this.layoutDoc.clipStart = this.trimStart;
+ this.layoutDoc.clipEnd = this.trimEnd;
+ this._trimming = TrimScope.None;
+ }
+
+ @action
+ public setZoom(zoom: number) {
+ this._zoomFactor = zoom;
}
+ anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
+ anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null);
+
+ // converts screen pixel offset to time
+ toTimeline = (screen_delta: number, width: number) => {
+ return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart));
+ };
+
rangeClickScript = () => CollectionStackedTimeline.RangeScript;
rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
- // for creating key anchors with key events
+ // handles key events for for creating key anchors, scrubbing, exiting trim
@action
keyEvents = (e: KeyboardEvent) => {
if (
- !(e.target instanceof HTMLInputElement) &&
+ // need to include range inputs because after dragging video time slider it becomes target element
+ !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
this.props.isSelected(true)
) {
+ // if shift pressed scrub 1 second otherwise 1/10th
+ const jump = e.shiftKey ? 1 : 0.1;
switch (e.key) {
- case " ":
+ case ' ':
if (!CollectionStackedTimeline.SelectingRegion) {
this._markerStart = this._markerEnd = this.currentTime;
CollectionStackedTimeline.SelectingRegion = this;
} else {
- CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.props.fieldKey,
- this.props.startTag,
- this.props.endTag,
- this.currentTime
- );
+ this._markerEnd = this.currentTime;
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd);
+ this._markerEnd = undefined;
CollectionStackedTimeline.SelectingRegion = undefined;
}
+ e.stopPropagation();
+ break;
+ case 'Escape':
+ // abandons current trim
+ this._trimStart = this.clipStart;
+ this._trimStart = this.clipEnd;
+ this._trimming = TrimScope.None;
+ e.stopPropagation();
+ break;
+ case 'ArrowLeft':
+ this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
+ e.stopPropagation();
+ break;
+ case 'ArrowRight':
+ this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
+ e.stopPropagation();
+ break;
}
}
- }
+ };
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- const linkTime = NumCast(
- la2[this.props.startTag],
- NumCast(la1[this.props.startTag])
- );
+ const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
@@ -204,28 +226,24 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return { la1, la2, linkTime };
}
- // starting the drag event for anchor resizing
+ // handles dragging selection to create markers
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
const clientX = e.clientX;
+ const diff = rect ? clientX - rect?.x : null;
+ const shiftKey = e.shiftKey;
if (rect && this.props.isContentActive()) {
const wasPlaying = this.props.playing();
if (wasPlaying) this.props.Pause();
- const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
+ var wasSelecting = this._markerEnd !== undefined;
setupMoveUpEvents(
this,
e,
- action((e) => {
- if (
- !wasSelecting &&
- CollectionStackedTimeline.SelectingRegion !== this
- ) {
- this._markerStart = this._markerEnd = this.toTimeline(
- clientX - rect.x,
- rect.width
- );
- CollectionStackedTimeline.SelectingRegion = this;
+ action(e => {
+ if (!wasSelecting) {
+ this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
+ wasSelecting = true;
}
this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
return false;
@@ -237,117 +255,125 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerStart = this._markerEnd;
this._markerEnd = tmp;
}
- if (
- !isClick &&
- CollectionStackedTimeline.SelectingRegion === this &&
- Math.abs(movement[0]) > 15 &&
- !this.props.trimming
- ) {
- const anchor = CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.props.fieldKey,
- this.props.startTag,
- this.props.endTag,
- this._markerStart,
- this._markerEnd
- );
+ if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
+ const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd);
setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
}
- (!isClick || !wasSelecting) &&
- (CollectionStackedTimeline.SelectingRegion = undefined);
+ (!isClick || !wasSelecting) && (this._markerEnd = undefined);
}),
(e, doubleTap) => {
- this.props.select(false);
- e.shiftKey &&
- CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.props.fieldKey,
- this.props.startTag,
- this.props.endTag,
- this.currentTime
- );
- !wasPlaying && doubleTap && this.props.Play();
+ if (e.button !== 2) {
+ this.props.select(false);
+ !wasPlaying && doubleTap && this.props.Play();
+ }
},
this.props.isSelected(true) || this.props.isContentActive(),
undefined,
() => {
- !wasPlaying &&
- (this.props.trimming && this.duration ?
- this.props.setTime(((clientX - rect.x) / rect.width) * this.duration)
- :
- this.props.setTime(((clientX - rect.x) / rect.width) * this.props.trimDuration + this.trimStart)
- );
+ if (shiftKey) {
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
+ } else {
+ !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ }
}
);
}
+ };
- }
+ @action
+ onHover = (e: React.MouseEvent): void => {
+ e.stopPropagation();
+ const rect = this._timeline?.getBoundingClientRect();
+ const clientX = e.clientX;
+ if (rect) {
+ this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
+ if (this.dataDoc.thumbnails) {
+ const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails);
+ const thumbnails = StrListCast(this.dataDoc.thumbnails);
+ const imgField = thumbnails?.length > 0 ? new ImageField(thumbnails[nearest]) : undefined;
+ this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined;
+ }
+ }
+ };
+ // for dragging trim start handle
@action
trimLeft = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.movementX;
setupMoveUpEvents(
this,
e,
action((e, [], []) => {
if (rect && this.props.isContentActive()) {
- this.props.setStartTrim(Math.min(
- Math.max(
- this.trimStart + (e.movementX / rect.width) * this.duration,
- 0
- ),
- this.trimEnd - this.minLength
- ));
+ this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
}
return false;
}),
emptyFunction,
action((e, doubleTap) => {
- if (doubleTap) {
- this.props.setStartTrim(0);
- }
+ doubleTap && (this._trimStart = this.clipStart);
})
);
- }
+ };
+ // for dragging trim end handle
@action
trimRight = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.movementX;
setupMoveUpEvents(
this,
e,
action((e, [], []) => {
if (rect && this.props.isContentActive()) {
- this.props.setEndTrim(Math.max(
- Math.min(
- this.trimEnd + (e.movementX / rect.width) * this.duration,
- this.duration
- ),
- this.trimStart + this.minLength
- ));
+ this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
}
return false;
}),
emptyFunction,
action((e, doubleTap) => {
- if (doubleTap) {
- this.props.setEndTrim(this.duration);
- }
+ doubleTap && (this._trimEnd = this.clipEnd);
})
);
- }
+ };
+
+ // for rendering scrolling when timeline zoomed
+ @action
+ setScroll = (e: React.UIEvent) => {
+ e.stopPropagation();
+ this._scroll = this._timelineWrapper!.scrollLeft;
+ };
+ // smooth scrolls to time like when following links overflowed due to zoom
+ @action
+ scrollToTime = (time: number) => {
+ if (this._timelineWrapper) {
+ if (time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) {
+ this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth());
+ smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
+ } else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) {
+ this._scroll = (time / this.timelineContentWidth) * this.clipDuration;
+ smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
+ }
+ }
+ };
+
+ // handles dragging and dropping markers in timeline
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
- if (!de.embedKey && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
+ if (!de.embedKey && this.props.Document._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
-
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
+ const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const x = localPt[0] - docDragData.offset[0];
+ const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
+ docDragData.droppedDocuments.forEach(drop => {
+ const anchorEnd = this.anchorEnd(drop);
+ if (anchorEnd !== undefined) {
+ Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
+ }
+ Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
+ });
return true;
}
@@ -355,34 +381,31 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0);
return false;
- }
+ };
+ // creates marker on timeline
@undoBatch
@action
- static createAnchor(
- rootDoc: Doc,
- dataDoc: Doc,
- fieldKey: string,
- startTag: string,
- endTag: string,
- anchorStartTime?: number,
- anchorEndTime?: number,
- docAnchor?: Doc
- ) {
+ static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number, docAnchor?: Doc) {
if (anchorStartTime === undefined) return rootDoc;
- const anchor = docAnchor ?? Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(
- `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`
- ) as any,
- _stayInCollection: true,
- useLinkSmallAnchor: true,
- hideLinkButton: true,
- annotationOn: rootDoc,
- _timelineLabel: true,
- });
+ const anchor =
+ docAnchor ??
+ Docs.Create.LabelDocument({
+ title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any,
+ _minFontSize: 12,
+ _maxFontSize: 24,
+ _singleLine: false,
+ _stayInCollection: true,
+ useLinkSmallAnchor: true,
+ hideLinkButton: true,
+ _isLinkButton: true,
+ annotationOn: rootDoc,
+ _timelineLabel: true,
+ borderRounding: anchorEndTime === undefined ? '100%' : undefined,
+ });
Doc.GetProto(anchor)[startTag] = anchorStartTime;
Doc.GetProto(anchor)[endTag] = anchorEndTime;
- if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) {
+ if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) {
Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
} else {
dataDoc[fieldKey] = new List<Doc>([anchor]);
@@ -396,12 +419,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlayAnchors) {
if (this.props.playing()) this.props.Pause();
- else this.props.playFrom(seekTimeInSeconds, endTime);
+ else {
+ this.props.playFrom(seekTimeInSeconds, endTime);
+ this.scrollToTime(seekTimeInSeconds);
+ }
} else {
- if (
- seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) &&
- endTime > NumCast(this.layoutDoc._currentTimecode)
- ) {
+ if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) {
if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
this.props.Pause();
} else {
@@ -409,62 +432,47 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
} else {
this.props.playFrom(seekTimeInSeconds, endTime);
+ this.scrollToTime(seekTimeInSeconds);
}
}
return { select: true };
- }
+ };
@action
clickAnchor = (anchorDoc: Doc, clientX: number) => {
if (anchorDoc.isLinkButton) {
- LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
+ LinkFollower.FollowLink(undefined, anchorDoc, this.props, false);
}
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
const endTime = this.anchorEnd(anchorDoc);
- if (
- seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 &&
- endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4
- ) {
+ if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) {
if (this.props.playing()) this.props.Pause();
else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
else if (!this.layoutDoc.autoPlayAnchors) {
const rect = this._timeline?.getBoundingClientRect();
- rect &&
- this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
} else {
if (this.layoutDoc.autoPlayAnchors) {
this.props.playFrom(seekTimeInSeconds, endTime);
- }
- else {
+ } else {
this.props.setTime(seekTimeInSeconds);
}
}
return { select: true };
- }
+ };
// makes sure no anchors overlaps each other by setting the correct position and width
- getLevel = (
- m: Doc,
- placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]
- ) => {
- const timelineContentWidth = this.props.PanelWidth();
+ getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => {
+ const timelineContentWidth = this.timelineContentWidth;
const x1 = this.anchorStart(m);
- const x2 = this.anchorEnd(
- m,
- x1 + (10 / timelineContentWidth) * this.duration
- );
+ const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration);
let max = 0;
const overlappedLevels = new Set(
- placed.map((p) => {
+ placed.map(p => {
const y1 = p.anchorStartTime;
const y2 = p.anchorEndTime;
- if (
- (x1 >= y1 && x1 <= y2) ||
- (x2 >= y1 && x2 <= y2) ||
- (y1 >= x1 && y1 <= x2) ||
- (y2 >= x1 && y2 <= x2)
- ) {
+ if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) || (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) {
max = Math.max(max, p.level);
return p.level;
}
@@ -475,40 +483,42 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
placed.push({ anchorStartTime: x1, anchorEndTime: x2, level });
return level;
- }
+ };
dictationHeightPercent = 50;
- dictationHeight = () =>
- (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100
- timelineContentHeight = () =>
- (this.props.PanelHeight() * this.dictationHeightPercent) / 100
- dictationScreenToLocalTransform = () =>
- this.props
- .ScreenToLocalTransform()
- .translate(0, -this.timelineContentHeight())
+ dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
+
+ @computed get timelineContentHeight() {
+ return (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
+ }
+ @computed get timelineContentWidth() {
+ return this.props.PanelWidth() * this.zoomFactor;
+ } // subtract size of container border
+
+ dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+
+ currentTimecode = () => this.currentTime;
+
@computed get renderDictation() {
const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
return !dictation ? null : (
<div
style={{
- position: "absolute",
- height: "100%",
- top: this.timelineContentHeight(),
+ position: 'absolute',
+ height: '100%',
+ top: this.timelineContentHeight,
background: Colors.LIGHT_BLUE,
- }}
- >
+ }}>
<DocumentView
- {...OmitKeys(this.props, [
- "NativeWidth",
- "NativeHeight",
- "setContentView",
- ]).omit}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
Document={dictation}
PanelHeight={this.dictationHeight}
isAnnotationOverlay={true}
isDocumentActive={returnFalse}
select={emptyFunction}
- scaling={returnOne}
+ NativeDimScaling={returnOne}
xMargin={25}
yMargin={10}
ScreenToLocalTransform={this.dictationScreenToLocalTransform}
@@ -517,149 +527,161 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
moveDocument={returnFalse}
addDocument={returnFalse}
CollectionView={undefined}
- renderDepth={this.props.renderDepth + 1}
- ></DocumentView>
+ renderDepth={this.props.renderDepth + 1}></DocumentView>
</div>
);
}
- @computed get renderAudioWaveform() {
- return !this.props.mediaPath ? null : (
- <div className="collectionStackedTimeline-waveform">
- <AudioWaveform
- duration={this.duration}
- mediaPath={this.props.mediaPath}
- layoutDoc={this.layoutDoc}
- PanelHeight={this.timelineContentHeight}
- trimming={this.props.trimming}
- />
- </div>
+
+ // renders selection region on timeline
+ @computed get selectionContainer() {
+ const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd;
+ return markerEnd === undefined ? null : (
+ <div
+ className="collectionStackedTimeline-selector"
+ style={{
+ left: `${((Math.min(this._markerStart, markerEnd) - this.trimStart) / this.trimDuration) * 100}%`,
+ width: `${(Math.abs(this._markerStart - markerEnd) / this.trimDuration) * 100}%`,
+ }}
+ />
);
}
- currentTimecode = () => this.currentTime;
render() {
- const timelineContentWidth = this.props.PanelWidth();
const overlaps: {
anchorStartTime: number;
anchorEndTime: number;
level: number;
}[] = [];
- const drawAnchors = this.childDocs.map((anchor) => ({
+ const drawAnchors = this.childDocs.map(anchor => ({
level: this.getLevel(anchor, overlaps),
anchor,
}));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
- const isActive =
- this.props.isContentActive() || this.props.isSelected(false);
- return (<div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined }}>
- <div
- className="collectionStackedTimeline"
- ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
- onClick={(e) => isActive && StopEvent(e)}
- onPointerDown={(e) => isActive && this.onPointerDownTimeline(e)}
- >
- {drawAnchors.map((d) => {
-
- const start = this.anchorStart(d.anchor);
- const end = this.anchorEnd(
- d.anchor,
- start + (10 / timelineContentWidth) * this.duration
- );
- const left = this.props.trimming ?
- (start / this.duration) * timelineContentWidth
- : (start - this.trimStart) / this.props.trimDuration * timelineContentWidth;
- const top = (d.level / maxLevel) * this.timelineContentHeight();
- const timespan = end - start;
- const width = (timespan / this.props.trimDuration) * timelineContentWidth;
- const height = this.timelineContentHeight() / maxLevel;
- return this.props.Document.hideAnchors ? null : (
- <div
- className={"collectionStackedTimeline-marker-timeline"}
- key={d.anchor[Id]}
- style={{
- left,
- top,
- width: `${width}px`,
- height: `${height}px`,
- }}
- onClick={(e) => {
- this.props.playFrom(start, this.anchorEnd(d.anchor));
- e.stopPropagation();
- }}
- >
- <StackedTimelineAnchor
- {...this.props}
- mark={d.anchor}
- rangeClickScript={this.rangeClickScript}
- rangePlayScript={this.rangePlayScript}
- left={left}
- top={top}
- width={width}
- height={height}
- toTimeline={this.toTimeline}
- layoutDoc={this.layoutDoc}
- currentTimecode={this.currentTimecode}
- _timeline={this._timeline}
- stackedTimeline={this}
- trimStart={this.trimStart}
- trimEnd={this.trimEnd}
- />
- </div>
- );
- })}
- {!this.props.trimming && this.selectionContainer}
- {this.renderAudioWaveform}
- {this.renderDictation}
-
+ return (
+ <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}>
<div
- className="collectionStackedTimeline-current"
- style={{
- left: this.props.trimming
- ? `${(this.currentTime / this.duration) * 100}%`
- : `${(this.currentTime - this.trimStart) / (this.trimEnd - this.trimStart) * 100}%`,
- }}
- />
-
- {this.props.trimming && (
- <>
- <div
- className="collectionStackedTimeline-trim-shade"
- style={{ width: `${(this.trimStart / this.duration) * 100}%` }}
- ></div>
+ className="collectionStackedTimeline-timelineContainer"
+ style={{ width: this.props.PanelWidth() }}
+ onWheel={e => e.stopPropagation()}
+ onScroll={this.setScroll}
+ onMouseMove={e => this.isContentActive() && this.onHover(e)}
+ ref={wrapper => (this._timelineWrapper = wrapper)}>
+ <div
+ className="collectionStackedTimeline"
+ ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+ onClick={e => this.isContentActive() && StopEvent(e)}
+ onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)}
+ style={{ width: this.timelineContentWidth }}>
+ {drawAnchors.map(d => {
+ const start = this.anchorStart(d.anchor);
+ const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration);
+ if (end < this.clipStart || start > this.clipEnd) return null;
+ const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0);
+ const top = (d.level / maxLevel) * this.props.PanelHeight();
+ const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
+ const width = (timespan / this.clipDuration) * this.timelineContentWidth;
+ const height = this.props.PanelHeight() / maxLevel;
+ return this.props.Document.hideAnchors ? null : (
+ <div
+ className={'collectionStackedTimeline-marker-timeline'}
+ key={d.anchor[Id]}
+ style={{
+ left,
+ top,
+ width: `${width}px`,
+ height: `${height}px`,
+ }}
+ onClick={e => {
+ this.props.playFrom(start, this.anchorEnd(d.anchor));
+ e.stopPropagation();
+ }}>
+ <StackedTimelineAnchor
+ {...this.props}
+ mark={d.anchor}
+ rangeClickScript={this.rangeClickScript}
+ rangePlayScript={this.rangePlayScript}
+ left={left - this._scroll}
+ top={top}
+ width={width}
+ height={height}
+ toTimeline={this.toTimeline}
+ layoutDoc={this.layoutDoc}
+ // isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+ currentTimecode={this.currentTimecode}
+ _timeline={this._timeline}
+ stackedTimeline={this}
+ trimStart={this.trimStart}
+ trimEnd={this.trimEnd}
+ />
+ </div>
+ );
+ })}
+ {!this.IsTrimming && this.selectionContainer}
+ <AudioWaveform
+ rawDuration={this.props.rawDuration}
+ duration={this.clipDuration}
+ mediaPath={this.props.mediaPath}
+ layoutDoc={this.layoutDoc}
+ clipStart={this.clipStart}
+ clipEnd={this.clipEnd}
+ zoomFactor={this.zoomFactor}
+ PanelHeight={this.timelineContentHeight}
+ PanelWidth={this.timelineContentWidth}
+ />
+ {/* {this.renderDictation} */}
<div
- className="collectionStackedTimeline-trim-controls"
+ className="collectionStackedTimeline-hover"
style={{
- left: `${(this.trimStart / this.duration) * 100}%`,
- width: `${((this.trimEnd - this.trimStart) / this.duration) * 100
- }%`,
+ left: `${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%`,
}}
- >
- <div
- className="collectionStackedTimeline-trim-handle"
- onPointerDown={this.trimLeft}
- ></div>
- <div
- className="collectionStackedTimeline-trim-handle"
- onPointerDown={this.trimRight}
- ></div>
- </div>
+ />
<div
- className="collectionStackedTimeline-trim-shade"
+ className="collectionStackedTimeline-current"
style={{
- left: `${(this.trimEnd / this.duration) * 100}%`,
- width: `${((this.duration - this.trimEnd) / this.duration) * 100
- }%`,
+ left: `${((this.currentTime - this.clipStart) / this.clipDuration) * 100}%`,
}}
- ></div>
- </>
- )}
+ />
+
+ {this.IsTrimming !== TrimScope.None && (
+ <>
+ <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div>
+
+ <div
+ className="collectionStackedTimeline-trim-controls"
+ style={{
+ left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`,
+ width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`,
+ }}>
+ <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft}></div>
+ <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight}></div>
+ </div>
+
+ <div
+ className="collectionStackedTimeline-trim-shade"
+ style={{
+ left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`,
+ width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`,
+ }}></div>
+ </>
+ )}
+ </div>
+ </div>
+ <div className="timeline-hoverUI" style={{ left: `calc(${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%` }}>
+ <div className="hoverTime">{formatTime(this._hoverTime - this.clipStart)}</div>
+ {this._thumbnail && <img className="videoBox-thumbnail" src={this._thumbnail} />}
+ </div>
</div>
- </div>);
+ );
}
}
+/**
+ * StackedTimelineAnchor
+ * creates the anchors to display markers, links, and embedded documents on timeline
+ */
+
interface StackedTimelineAnchorProps {
mark: Doc;
rangeClickScript: () => ScriptField;
@@ -675,6 +697,7 @@ interface StackedTimelineAnchorProps {
endTag: string;
renderDepth: number;
layoutDoc: Doc;
+ isDocumentActive?: () => boolean;
ScreenToLocalTransform: () => Transform;
_timeline: HTMLDivElement | null;
focus: DocFocusFunc;
@@ -684,54 +707,53 @@ interface StackedTimelineAnchorProps {
trimStart: number;
trimEnd: number;
}
+
@observer
class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
_lastTimecode: number;
_disposer: IReactionDisposer | undefined;
+
constructor(props: any) {
super(props);
this._lastTimecode = this.props.currentTimecode();
}
+
+ // updates marker document title to reflect correct timecodes
+ computeTitle = () => {
+ if (this.props.mark.type !== DocumentType.LABEL) return undefined;
+ const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
+ const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+ return `#${formatTime(start)}-${formatTime(end)}`;
+ };
+
componentDidMount() {
this._disposer = reaction(
() => this.props.currentTimecode(),
- (time) => {
- const dictationDoc = Cast(
- this.props.layoutDoc["data-dictation"],
- Doc,
- null
- );
- const isDictation =
- dictationDoc &&
- DocListCast(this.props.mark.links).some(
- (link) =>
- Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc
- );
+ time => {
+ const dictationDoc = Cast(this.props.layoutDoc['data-dictation'], Doc, null);
+ const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc);
if (
!LightboxView.LightboxDoc &&
// bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront.
// for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
/*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
+ !this.props.layoutDoc.dontAutoFollowLinks &&
DocListCast(this.props.mark.links).length &&
time > NumCast(this.props.mark[this.props.startTag]) &&
time < NumCast(this.props.mark[this.props.endTag]) &&
this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5
) {
- LinkManager.FollowLink(
- undefined,
- this.props.mark,
- this.props as any as DocumentViewProps,
- false,
- true
- );
+ LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true);
}
this._lastTimecode = time;
}
);
}
+
componentWillUnmount() {
this._disposer?.();
}
+
// starting the drag event for anchor resizing
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
this.props._timeline?.setPointerCapture(e.pointerId);
@@ -739,61 +761,50 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
const rect = (e.target as any).getBoundingClientRect();
return this.props.toTimeline(e.clientX - rect.x, rect.width);
};
- const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
- const timelineOnly =
- Cast(anchor[this.props.startTag], "number", null) !== undefined;
+ const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
+ const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== undefined;
if (timelineOnly) {
- Doc.SetInPlace(
- anchor,
- left ? this.props.startTag : this.props.endTag,
- time,
- true
- );
- }
- else {
- left
- ? (anchor._timecodeToShow = time)
- : (anchor._timecodeToHide = time);
+ if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined;
+ Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
+ if (!left) Doc.SetInPlace(anchor, 'borderRounding', time !== undefined ? undefined : '100%', true);
+ } else {
+ anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
}
return false;
};
+ var undo: UndoManager.Batch | undefined;
+
setupMoveUpEvents(
this,
e,
- (e) => changeAnchor(anchor, left, newTime(e)),
- (e) => {
+ e => {
+ if (!undo) undo = UndoManager.StartBatch('drag anchor');
+ this.props.setTime(newTime(e));
+ return changeAnchor(anchor, left, newTime(e));
+ },
+ e => {
this.props.setTime(newTime(e));
this.props._timeline?.releasePointerCapture(e.pointerId);
+ undo?.end();
},
emptyFunction
);
- }
-
- @action
- computeTitle = () => {
- const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
- const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
- return `#${formatTime(start)}-${formatTime(end)}`;
- }
+ };
+
+ // context menu
+ contextMenuItems = () => {
+ const resetTitle = {
+ script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${this.props.startTag}"])`)!,
+ icon: 'folder-plus',
+ label: 'Reset Title',
+ };
+ return [resetTitle];
+ };
- renderInner = computedFn(function (
- this: StackedTimelineAnchor,
- mark: Doc,
- script: undefined | (() => ScriptField),
- doublescript: undefined | (() => ScriptField),
- x: number,
- y: number,
- width: number,
- height: number
- ) {
+ // renders anchor LabelBox
+ renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
const anchor = observable({ view: undefined as any });
- const focusFunc = (
- doc: Doc,
- willZoom?: boolean,
- scale?: number,
- afterFocus?: DocAfterFocusFunc,
- docTransform?: Transform
- ) => {
+ const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
this.props.playLink(mark);
this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
};
@@ -802,62 +813,45 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
view: (
<DocumentView
key="view"
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
ref={action((r: DocumentView | null) => (anchor.view = r))}
Document={mark}
DataDoc={undefined}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={undefined}
- LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())}
- isDocumentActive={returnFalse}
- PanelWidth={() => width}
- PanelHeight={() => height}
- ScreenToLocalTransform={() =>
- this.props.ScreenToLocalTransform().translate(-x, -y)
- }
+ LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
+ isDocumentActive={this.props.isDocumentActive}
+ PanelWidth={width}
+ PanelHeight={height}
+ fitWidth={returnTrue}
+ ScreenToLocalTransform={screenXf}
focus={focusFunc}
rootSelected={returnFalse}
onClick={script}
- onDoubleClick={
- this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript
- }
+ onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
ignoreAutoHeight={false}
hideResizeHandles={true}
bringToFront={emptyFunction}
+ contextMenuItems={this.contextMenuItems}
scriptContext={this.props.stackedTimeline}
/>
),
};
});
+ anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top);
+ width = () => this.props.width;
+ height = () => this.props.height;
+
render() {
- const inner = this.renderInner(
- this.props.mark,
- this.props.rangeClickScript,
- this.props.rangePlayScript,
- this.props.left,
- this.props.top,
- this.props.width,
- this.props.height
- );
+ const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
return (
<>
{inner.view}
- {!inner.anchor.view ||
- !SelectionManager.IsSelected(inner.anchor.view) ? null : (
+ {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : (
<>
- <div
- key="left"
- className="collectionStackedTimeline-left-resizer"
- onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
- />
- <div
- key="right"
- className="collectionStackedTimeline-resizer"
- onPointerDown={(e) =>
- this.onAnchorDown(e, this.props.mark, false)
- }
- />
+ <div key="left" className="collectionStackedTimeline-left-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
+ <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
</>
)}
</>
@@ -872,4 +866,4 @@ ScriptingGlobals.add(function min(num1: number, num2: number): number {
});
ScriptingGlobals.add(function max(num1: number, num2: number): number {
return Math.max(num1, num2);
-}); \ No newline at end of file
+});
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 78df932f9..7385f933b 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
.collectionMasonryView {
display: inline;
@@ -30,11 +30,10 @@
width: 90%;
margin: 5px;
font-size: 11px;
- background-color: $light-blue;
color: $medium-blue;
padding: 10px;
- border-radius: 10px;
- border: solid 2px $medium-blue;
+ border-radius: 5px;
+ border: solid 0.5px $medium-blue;
}
}
@@ -47,9 +46,9 @@
overflow-y: auto;
overflow-x: hidden;
flex-wrap: wrap;
- transition: top .5s;
+ transition: top 0.5s;
- >div {
+ > div {
position: relative;
display: block;
}
@@ -134,34 +133,8 @@
// Documents in stacking view
.collectionStackingView-columnDoc {
display: flex;
- // margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change
+ // margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change
position: relative;
-
- &:hover {
- .hoverButtons{
- opacity: 1;
- }
- }
-
- .hoverButtons {
- display: flex;
- opacity: 0;
- position: absolute;
- height: 100%;
- left: -35px;
- justify-content: center;
- align-items: center;
- padding: 0px 10px;
-
- .buttonWrapper {
- padding: 3px;
- border-radius: 3px;
-
- &:hover {
- background: rgba(0, 0, 0, 0.26);
- }
- }
- }
}
.collectionStackingView-masonryDoc {
@@ -202,7 +175,7 @@
span::before,
span::after {
- content: "";
+ content: '';
width: 50%;
border-top: dashed gray 1px;
position: relative;
@@ -243,7 +216,7 @@
.editableView-input:hover,
.editableView-container-editing:hover,
.editableView-container-editing-oneLine:hover {
- cursor: text
+ cursor: text;
}
.collectionStackingView-sectionHeader-subCont {
@@ -289,7 +262,7 @@
height: 100%;
display: none;
- [class*="css"] {
+ [class*='css'] {
max-width: 102px;
}
@@ -331,7 +304,7 @@
height: 100%;
display: none;
- [class*="css"] {
+ [class*='css'] {
max-width: 102px;
}
@@ -340,7 +313,6 @@
}
.collectionStackingView-optionPicker {
-
.optionOptions {
display: inline;
}
@@ -400,7 +372,7 @@
.editableView-input:hover,
.editableView-container-editing:hover,
.editableView-container-editing-oneLine:hover {
- cursor: text
+ cursor: text;
}
.editableView-input {
@@ -453,7 +425,7 @@
top: 4px;
border-radius: 50% 50%;
background-color: #fff;
- content: " ";
+ content: ' ';
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.26);
-webkit-transform: scale(1);
@@ -483,7 +455,6 @@
}
@media only screen and (max-device-width: 480px) {
-
.collectionStackingView .collectionStackingView-columnDragger,
.collectionMasonryView .collectionStackingView-columnDragger {
width: 0.1;
@@ -491,4 +462,4 @@
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 90b1eb71f..ef68cadd7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,35 +1,36 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { CursorProperty } from "csstype";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DragManager, dropActionType } from "../../util/DragManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { EditableView } from "../EditableView";
-import { LightboxView } from "../LightboxView";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView";
-import { StyleProp } from "../StyleProvider";
-import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
-import "./CollectionStackingView.scss";
-import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
-import { CollectionSubView } from "./CollectionSubView";
-import { CollectionViewType } from "./CollectionView";
-const _global = (window /* browser */ || global /* node */) as any;
-
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { CursorProperty } from 'csstype';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { CollectionViewType } from '../../documents/DocumentTypes';
+import { DragManager, dropActionType } from '../../util/DragManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { EditableView } from '../EditableView';
+import { LightboxView } from '../LightboxView';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { StyleProp } from '../StyleProvider';
+import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
+import './CollectionStackingView.scss';
+import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
+import { CollectionSubView } from './CollectionSubView';
+const _global = (window /* browser */ || global) /* node */ as any;
export type collectionStackingViewProps = {
chromeHidden?: boolean;
@@ -42,62 +43,85 @@ export type collectionStackingViewProps = {
@observer
export class CollectionStackingView extends CollectionSubView<Partial<collectionStackingViewProps>>() {
_masonryGridRef: HTMLDivElement | null = null;
- // used in a column dragger, likely due for the masonry grid view. We want to use this
+ // used in a column dragger, likely due for the masonry grid view. We want to use this
_draggerRef = React.createRef<HTMLDivElement>();
// Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view
_pivotFieldDisposer?: IReactionDisposer;
// Seems like we cause reaction in MobX get rid of our height once we exit this view
_autoHeightDisposer?: IReactionDisposer;
// keeping track of documents. Updated on internal and external drops. What's the difference?
- _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = [];
+ _docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = [];
// Doesn't look like this field is being used anywhere. Obsolete?
_columnStart: number = 0;
// map of node headers to their heights. Used in Masonry
@observable _heightMap = new Map<string, number>();
// Assuming that this is the current css cursor style
- @observable _cursor: CursorProperty = "grab";
+ @observable _cursor: CursorProperty = 'grab';
// gets reset whenever we scroll. Not sure what it is
@observable _scroll = 0; // used to force the document decoration to update when scrolling
// does this mean whether the browser is hidden? Or is chrome something else entirely?
- @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); }
+ @computed get chromeHidden() {
+ return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
+ }
// it looks like this gets the column headers that Mehek was showing just now
- @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); }
+ @computed get columnHeaders() {
+ return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null);
+ }
// Still not sure what a pivot is, but it appears that we can actually filter docs somehow?
- @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); }
+ @computed get pivotField() {
+ return StrCast(this.layoutDoc._pivotField);
+ }
// filteredChildren is what you want to work with. It's the list of things that you're currently displaying
- @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); }
+ @computed get filteredChildren() {
+ return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout);
+ }
// how much margin we give the header
- @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); }
- @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
- @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); }
- @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
+ @computed get headerMargin() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
+ }
+ @computed get xMargin() {
+ return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, 0.05 * this.props.PanelWidth()));
+ }
+ @computed get yMargin() {
+ return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5);
+ } // 2 * this.gridGap)); }
+ @computed get gridGap() {
+ return NumCast(this.layoutDoc._gridGap, 10);
+ }
// are we stacking or masonry?
- @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === (CollectionViewType.Stacking || CollectionViewType.NoteTaking); }
+ @computed get isStackingView() {
+ return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking;
+ }
// this is the number of StackingViewFieldColumns that we have
- @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
+ @computed get numGroupColumns() {
+ return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1;
+ }
// reveals a button to add a group in masonry view
- @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; }
+ @computed get showAddAGroup() {
+ return this.pivotField && !this.chromeHidden;
+ }
// columnWidth handles the margin on the left and right side of the documents
@computed get columnWidth() {
- return Math.min(this.props.PanelWidth() - 2 * this.xMargin,
- this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
+ return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
+ }
+
+ @computed get NodeWidth() {
+ return this.props.PanelWidth() - this.gridGap;
}
-
- @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
constructor(props: any) {
super(props);
if (this.columnHeaders === undefined) {
- // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out?
- // here we're making an empty list of column headers (again, what Mehek showed us)
+ // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out?
+ // here we're making an empty list of column headers (again, what Mehek showed us)
this.layoutDoc._columnHeaders = new List<SchemaHeaderField>();
}
}
// TODO: plj - these are the children
children = (docs: Doc[]) => {
- //TODO: can somebody explain me to what exactly TraceMobX is?
+ //TODO: can somebody explain me to what exactly TraceMobX is?
TraceMobx();
// appears that we are going to reset the _docXfs. TODO: what is Xfs?
this._docXfs.length = 0;
@@ -109,15 +133,17 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// just getting the style
const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
// So we're choosing whether we're going to render a column or a masonry doc
- return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} style={style} >
- {this.getDisplayDoc(d, width)}
- </div>;
+ return (
+ <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
+ {this.getDisplayDoc(d, width)}
+ </div>
+ );
});
- }
+ };
@action
setDocHeight = (key: string, sectionHeight: number) => {
this._heightMap.set(key, sectionHeight);
- }
+ };
// is sections that all collections inherit? I think this is how we show the masonry/columns
//TODO: this seems important
@@ -126,7 +152,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
if (this.columnHeaders === undefined) {
- setTimeout(() => this.layoutDoc._columnHeaders = new List<SchemaHeaderField>(), 0);
+ setTimeout(() => (this.layoutDoc._columnHeaders = new List<SchemaHeaderField>()), 0);
return new Map<SchemaHeaderField, Doc[]>();
}
const columnHeaders = Array.from(this.columnHeaders);
@@ -142,8 +168,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const existingHeader = columnHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`));
if (existingHeader) {
fields.get(existingHeader)!.push(d);
- }
- else {
+ } else {
const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`);
fields.set(newSchemaHeader, [d]);
columnHeaders.push(newSchemaHeader);
@@ -153,13 +178,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// remove all empty columns if hideHeadings is set
// we will want to have something like this, so that we can hide columns and add them back in
if (this.layoutDoc._columnsHideIfEmpty) {
- Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => {
- fields.delete(header);
- columnHeaders.splice(columnHeaders.indexOf(header), 1);
- changed = true;
- });
+ Array.from(fields.keys())
+ .filter(key => !fields.get(key)!.length)
+ .map(header => {
+ fields.delete(header);
+ columnHeaders.splice(columnHeaders.indexOf(header), 1);
+ changed = true;
+ });
}
- changed && setTimeout(action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), 0);
+ changed &&
+ setTimeout(
+ action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)),
+ 0
+ );
return fields;
}
@@ -169,14 +200,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// reset section headers when a new filter is inputted
this._pivotFieldDisposer = reaction(
() => this.pivotField,
- () => this.layoutDoc._columnHeaders = new List()
+ () => (this.layoutDoc._columnHeaders = new List())
+ );
+ this._autoHeightDisposer = reaction(
+ () => this.layoutDoc._autoHeight,
+ autoHeight =>
+ autoHeight &&
+ this.props.setHeight?.(
+ Math.min(
+ NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
+ this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0))
+ )
+ )
);
- //TODO: where the heck are we getting filters from?
- this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight,
- autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
- this.headerMargin + (this.isStackingView ?
- Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) :
- this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0)))));
}
componentWillUnmount() {
@@ -188,46 +224,51 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@action
moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
- }
+ };
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
- }
+ };
- @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
+ @computed get onChildClickHandler() {
+ return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+ }
+ @computed get onChildDoubleClickHandler() {
+ return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+ }
addDocTab = (doc: Doc, where: string) => {
- if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
return true;
}
return this.props.addDocTab(doc, where);
- }
+ };
scrollToBottom = () => {
smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight);
- }
+ };
// let's dive in and get the actual document we want to drag/move around
focusDocument = (doc: Doc, options?: DocFocusOptions) => {
Doc.BrushDoc(doc);
let focusSpeed = 0;
- const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]);
+ const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
if (found) {
const top = found.getBoundingClientRect().top;
const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
if (Math.floor(localTop[1]) !== 0) {
- smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+ smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
}
}
- const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing;
+ const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing;
this.props.focus(this.rootDoc, {
- willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
+ willZoom: options?.willZoom,
+ scale: options?.scale,
+ afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)),
});
- }
+ };
styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
if (property === StyleProp.Opacity && doc) {
@@ -239,81 +280,97 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
}
return this.props.styleProvider?.(doc, props, property);
- }
+ };
+ @undoBatch
+ @action
+ onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ const docView = fieldProps.DocumentView?.();
+ if (docView && ['Enter'].includes(e.key) && e.ctrlKey) {
+ e.stopPropagation?.();
+ const below = !e.altKey && e.key !== 'Tab';
+ const layoutKey = StrCast(docView.LayoutFieldKey);
+ const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+ const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
+ newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+ if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) {
+ newDoc[layoutKey] = docView.rootDoc[layoutKey];
+ }
+ Doc.GetProto(newDoc).text = undefined;
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ return this.addDocument?.(newDoc);
+ }
+ };
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive));
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
getDisplayDoc(doc: Doc, width: () => number) {
- const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
+ const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
let dref: Opt<DocumentView>;
const stackedDocTransform = () => this.getDocTransform(doc, dref);
this._docXfs.push({ stackedDocTransform, width, height });
//DocumentView is how the node will be rendered
- return <DocumentView ref={r => dref = r || undefined}
- Document={doc}
- DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
- renderDepth={this.props.renderDepth + 1}
- PanelWidth={width}
- PanelHeight={height}
- styleProvider={this.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={this.props.docViewPath}
- fitWidth={this.props.childFitWidth}
- isContentActive={emptyFunction}
- isDocumentActive={this.isContentActive}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- freezeDimensions={this.props.childFreezeDimensions}
- NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
- NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined}
- dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
- dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
- rootSelected={this.rootSelected}
- showTitle={this.props.childShowTitle}
- dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
- onClick={this.onChildClickHandler}
- onDoubleClick={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={stackedDocTransform}
- focus={this.focusDocument}
- docFilters={this.childDocFilters}
- hideDecorationTitle={this.props.childHideDecorationTitle?.()}
- hideResizeHandles={this.props.childHideResizeHandles?.()}
- hideTitle={this.props.childHideTitle?.()}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.addDocTab}
- bringToFront={returnFalse}
- scriptContext={this.props.scriptContext}
- pinToPres={this.props.pinToPres}
- />;
+ return (
+ <DocumentView
+ ref={r => (dref = r || undefined)}
+ Document={doc}
+ DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
+ renderDepth={this.props.renderDepth + 1}
+ PanelWidth={width}
+ PanelHeight={height}
+ styleProvider={this.styleProvider}
+ docViewPath={this.props.docViewPath}
+ fitWidth={this.props.childFitWidth}
+ isContentActive={this.isChildContentActive}
+ onKey={this.onKeyDown}
+ isDocumentActive={this.isContentActive}
+ LayoutTemplate={this.props.childLayoutTemplate}
+ LayoutTemplateString={this.props.childLayoutString}
+ NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+ NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
+ dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined}
+ dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't autoHeight resize if dontRegisterView is set, but they need to.
+ rootSelected={this.rootSelected}
+ showTitle={this.props.childShowTitle}
+ dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+ onClick={this.onChildClickHandler}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ ScreenToLocalTransform={stackedDocTransform}
+ focus={this.focusDocument}
+ docFilters={this.childDocFilters}
+ hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+ hideResizeHandles={this.props.childHideResizeHandles?.()}
+ hideTitle={this.props.childHideTitle?.()}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.addDocTab}
+ bringToFront={returnFalse}
+ scriptContext={this.props.scriptContext}
+ pinToPres={this.props.pinToPres}
+ />
+ );
}
getDocTransform(doc: Doc, dref?: DocumentView) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
- // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
- return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
+ // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off
+ return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
}
getDocWidth(d?: Doc) {
if (!d) return 0;
const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- // TODO: pj - replace with a better way to calculate the margin
- let margin = 25;
- d.margin = 25;
- if (this.columnWidth < 150){
- margin = 0;
- }
- const maxWidth = (this.columnWidth / this.numGroupColumns) - (margin * 2);
+ const maxWidth = this.columnWidth / this.numGroupColumns;
if (!this.layoutDoc._columnsFill && !(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d))) {
return Math.min(d[WidthSym](), maxWidth);
}
@@ -322,74 +379,49 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
- const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc;
- const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1));
+ const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc;
+ const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
if (nw && nh) {
const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
- return Math.min(
- maxHeight,
- docWid * nh / nw);
+ return Math.min(maxHeight, (docWid * nh) / nw);
}
const childHeight = NumCast(childLayoutDoc._height);
- const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
+ const panelHeight = childLayoutDoc._fitWidth || this.props.childFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
return Math.min(childHeight, maxHeight, panelHeight);
}
// This following three functions must be from the view Mehek showed
columnDividerDown = (e: React.PointerEvent) => {
- runInAction(() => this._cursor = "grabbing");
- setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction);
- }
+ runInAction(() => (this._cursor = 'grabbing'));
+ setupMoveUpEvents(
+ this,
+ e,
+ this.onDividerMove,
+ action(() => (this._cursor = 'grab')),
+ emptyFunction
+ );
+ };
@action
onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
this.layoutDoc._columnWidth = Math.max(10, this.columnWidth + delta[0]);
return false;
- }
+ };
@computed get columnDragger() {
- return <div className="collectionStackingView-columnDragger" onPointerDown={this.columnDividerDown} ref={this._draggerRef}
- style={{ cursor: this._cursor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }} >
- <FontAwesomeIcon icon={"arrows-alt-h"} />
- </div>;
- }
-
- // TODO: plj
- @action
- onPointerOver = (e: React.PointerEvent) => {
- // console.log("hovering over something")
- if (DragManager.docsBeingDragged.length) {
- // essentially copying code from onInternalDrop for this:
- const doc = DragManager.docsBeingDragged[0]
- // console.log(doc[LayoutSym]())
-
- console.log(doc[DataSym])
- console.log(Doc.IndexOf(doc, this.childDocs))
-
- }
-
-
- }
-
- //used in onPointerOver to swap two nodes in the rendered filtered children list
- swapNodes = (i: number, j: number) => {
-
- }
-
- //plj added this
- @action
- onPointerDown = (e: React.PointerEvent) => {
-
+ return (
+ <div className="collectionStackingView-columnDragger" onPointerDown={this.columnDividerDown} ref={this._draggerRef} style={{ cursor: this._cursor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}>
+ <FontAwesomeIcon icon={'arrows-alt-h'} />
+ </div>
+ );
}
- // TODO: plj - look at this. Start with making changes to db, and then transition to client side
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- // Fairly confident that this is where the swapping of nodes in the various arrays happens
- console.log('drop')
+ // Fairly confident that this is where the swapping of nodes in the various arrays happens
const where = [de.x, de.y];
// start at -1 until we're sure we want to add it to the column
let dropInd = -1;
@@ -397,7 +429,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (de.complete.docDragData) {
// going to re-add the docs to the _docXFs based on position of where we just dropped
this._docXfs.map((cd, i) => {
- const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+ const pos = cd
+ .stackedDocTransform()
+ .inverse()
+ .transformPoint(-2 * this.gridGap, -2 * this.gridGap);
const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height());
if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) {
dropInd = i;
@@ -407,31 +442,29 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
});
const oldDocs = this.childDocs.length;
if (super.onInternalDrop(e, de)) {
- // check to see if we actually need anything to the new column of nodes (if droppedDocs != empty)
+ // check to see if we actually need anything to the new column of nodes (if droppedDocs != empty)
const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= oldDocs); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments; // if nothing was added to the end of the list, then presumably the dropped documents were already in the list, but possibly got reordered so we use them.
const docs = this.childDocList;
- // reset drag manager docs, because we just dropped
- DragManager.docsBeingDragged = [];
// still figuring out where to add the document
if (docs && newDocs.length) {
const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter;
- const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0);
+ const offset = newDocs.reduce((off, ndoc) => (this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off), 0);
newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1));
docs.splice(insertInd - offset, 0, ...newDocs);
}
+ // reset drag manager docs, because we just dropped
+ DragManager.docsBeingDragged.length = 0;
}
- } // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging
- else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
- const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" });
+ } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
+ const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
e.stopPropagation();
- }
- else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
+ } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
return false;
- }
+ };
@undoBatch
internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) {
@@ -443,16 +476,17 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return true;
}
+ /// an item from outside of Dash is being dropped onto this stacking view (e.g, a document from the file system)
@undoBatch
@action
- //What is the difference between internal and external drop?? Does internal mean we're dropping inside of a collection?
- // I take it back: external drop means we took it out of column/collection that we were just in
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
- console.log('external drop')
const where = [e.clientX, e.clientY];
let targInd = -1;
this._docXfs.map((cd, i) => {
- const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+ const pos = cd
+ .stackedDocTransform()
+ .inverse()
+ .transformPoint(-2 * this.gridGap, -2 * this.gridGap);
const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height());
if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
targInd = i;
@@ -468,134 +502,136 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
}
});
- }
- // sections are important
+ };
headings = () => Array.from(this.Sections);
refList: any[] = [];
// what a section looks like if we're in stacking view
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.pivotField;
- let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
+ let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
if (this.pivotField) {
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
}
- //TODO: I think that we only have one of these atm
- return <CollectionStackingViewFieldColumn
- unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
- observeHeight={ref => {
- if (ref) {
- this.refList.push(ref);
- this.observer = new _global.ResizeObserver(action((entries: any) => {
- if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
- const height = this.headerMargin +
- Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
- Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
- if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- this.props.setHeight(height);
- }
- }
- }));
- this.observer.observe(ref);
- }
- }}
- addDocument={this.addDocument}
- chromeHidden={this.chromeHidden}
- columnHeaders={this.columnHeaders}
- Document={this.props.Document}
- DataDoc={this.props.DataDoc}
- renderChildren={this.children}
- columnWidth={this.columnWidth}
- numGroupColumns={this.numGroupColumns}
- gridGap={this.gridGap}
- pivotField={this.pivotField}
- key={heading?.heading ?? ""}
- headings={this.headings}
- heading={heading?.heading ?? ""}
- headingObject={heading}
- docList={docList}
- yMargin={this.yMargin}
- type={type}
- createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.props.ScreenToLocalTransform}
- />;
- }
+ return (
+ <CollectionStackingViewFieldColumn
+ unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
+ observeHeight={ref => {
+ if (ref) {
+ this.refList.push(ref);
+ this.observer = new _global.ResizeObserver(
+ action((entries: any) => {
+ if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
+ if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
+ this.props.setHeight?.(height);
+ }
+ }
+ })
+ );
+ this.observer.observe(ref);
+ }
+ }}
+ addDocument={this.addDocument}
+ chromeHidden={this.chromeHidden}
+ columnHeaders={this.columnHeaders}
+ Document={this.props.Document}
+ DataDoc={this.props.DataDoc}
+ renderChildren={this.children}
+ columnWidth={this.columnWidth}
+ numGroupColumns={this.numGroupColumns}
+ gridGap={this.gridGap}
+ pivotField={this.pivotField}
+ key={heading?.heading ?? ''}
+ headings={this.headings}
+ heading={heading?.heading ?? ''}
+ headingObject={heading}
+ docList={docList}
+ yMargin={this.yMargin}
+ type={type}
+ createDropTarget={this.createDashEventsTarget}
+ screenToLocalTransform={this.props.ScreenToLocalTransform}
+ />
+ );
+ };
// what a section looks like if we're in masonry. Shouldn't actually need to use this.
sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => {
const key = this.pivotField;
- let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
+ let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
type = types[0];
}
- const rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length,
- Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
- return <CollectionMasonryViewFieldRow
- showHandle={first}
- Document={this.props.Document}
- chromeHidden={this.chromeHidden}
- pivotField={this.pivotField}
- unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)}
- observeHeight={(ref) => {
- if (ref) {
- this.refList.push(ref);
- this.observer = new _global.ResizeObserver(action((entries: any) => {
- if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
- const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
- this.props.setHeight(this.headerMargin + height);
- }
- }));
- this.observer.observe(ref);
- }
- }}
- key={heading ? heading.heading : ""}
- rows={rows}
- headings={this.headings}
- heading={heading ? heading.heading : ""}
- headingObject={heading}
- docList={docList}
- parent={this}
- type={type}
- createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.props.ScreenToLocalTransform}
- setDocHeight={this.setDocHeight}
- />;
- }
+ const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
+ return (
+ <CollectionMasonryViewFieldRow
+ showHandle={first}
+ Document={this.props.Document}
+ chromeHidden={this.chromeHidden}
+ pivotField={this.pivotField}
+ unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
+ observeHeight={ref => {
+ if (ref) {
+ this.refList.push(ref);
+ this.observer = new _global.ResizeObserver(
+ action((entries: any) => {
+ if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
+ const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0);
+ this.props.setHeight?.(this.headerMargin + height);
+ }
+ })
+ );
+ this.observer.observe(ref);
+ }
+ }}
+ key={heading ? heading.heading : ''}
+ rows={rows}
+ headings={this.headings}
+ heading={heading ? heading.heading : ''}
+ headingObject={heading}
+ docList={docList}
+ parent={this}
+ type={type}
+ createDropTarget={this.createDashEventsTarget}
+ screenToLocalTransform={this.props.ScreenToLocalTransform}
+ setDocHeight={this.setDocHeight}
+ />
+ );
+ };
+ /// add a new group category (column) to the active set of note categories. (e.g., if the pivot field is 'transportation', groups might be 'car', 'plane', 'bike', etc)
@action
- // What are we adding a group to?
addGroup = (value: string) => {
if (value && this.columnHeaders) {
const schemaHdrField = new SchemaHeaderField(value);
this.columnHeaders.push(schemaHdrField);
- DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]);
return true;
}
return false;
- }
+ };
sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => {
- const descending = StrCast(this.layoutDoc._columnsSort) === "descending";
+ const descending = StrCast(this.layoutDoc._columnsSort) === 'descending';
const firstEntry = descending ? b : a;
const secondEntry = descending ? a : b;
return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
- }
+ };
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
if (!e.isPropagationStopped()) {
const subItems: ContextMenuProps[] = [];
- subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" });
- subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
+ subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
+ subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+ subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
}
- }
+ };
- //
+ //
@computed get renderedSections() {
TraceMobx();
let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
@@ -603,8 +639,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const entries = Array.from(this.Sections.entries());
sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries;
}
- // a section will have a header and a list of docs. Ok cool.
- return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0));
+ return sections.map((section, i) => (this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0)));
}
@computed get buttonMenu() {
@@ -614,93 +649,90 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const width: number = NumCast(menuDoc._width, 30);
const height: number = NumCast(menuDoc._height, 30);
console.log(menuDoc.title, width, height);
- return (<div className="buttonMenu-docBtn"
- style={{ width: width, height: height }}>
- <DocumentView
- Document={menuDoc}
- DataDoc={menuDoc}
- isContentActive={this.props.isContentActive}
- isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={emptyFunction}
- rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={() => 35}
- PanelHeight={() => 35}
- renderDepth={this.props.renderDepth}
- focus={emptyFunction}
- styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={returnEmptyDoclist}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={this.props.docFilters}
- docRangeFilters={this.props.docRangeFilters}
- searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- />
- </div>
+ return (
+ <div className="buttonMenu-docBtn" style={{ width: width, height: height }}>
+ <DocumentView
+ Document={menuDoc}
+ DataDoc={menuDoc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={() => 35}
+ PanelHeight={() => 35}
+ renderDepth={this.props.renderDepth}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
);
}
}
+ @computed get nativeWidth() {
+ return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
+ }
+ @computed get nativeHeight() {
+ return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
+ }
- @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); }
- @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); }
-
- @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }
+ @computed get scaling() {
+ return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight;
+ }
- @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); }
+ @computed get backgroundEvents() {
+ return SnappingManager.GetIsDragging();
+ }
observer: any;
render() {
TraceMobx();
const editableViewProps = {
- GetValue: () => "",
+ GetValue: () => '',
SetValue: this.addGroup,
- // I don't recall ever seeing this add a group button
- contents: "+ ADD A GROUP"
+ contents: '+ ADD A GROUP',
};
const buttonMenu = this.rootDoc.buttonMenu;
const noviceExplainer = this.rootDoc.explainer;
return (
<>
- {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
- {buttonMenu ? this.buttonMenu : null}
- {Doc.UserDoc().noviceMode && noviceExplainer ?
- <div className="documentExplanation">
- {noviceExplainer}
- </div>
- : null
- }
- </div> : null}
- <div className="collectionStackingMasonry-cont" >
- <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
+ {buttonMenu || noviceExplainer ? (
+ <div className="documentButtonMenu">
+ {buttonMenu ? this.buttonMenu : null}
+ {Doc.noviceMode && noviceExplainer ? <div className="documentExplanation">{StrCast(noviceExplainer)}</div> : null}
+ </div>
+ ) : null}
+ <div className="collectionStackingMasonry-cont">
+ <div
+ className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
ref={this.createRef}
style={{
- overflowY: this.props.isContentActive() ? "auto" : "hidden",
+ overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: this.backgroundEvents ? "all" : undefined
+ pointerEvents: this.backgroundEvents ? 'all' : undefined,
}}
- onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
- onPointerOver={this.onPointerOver}
- onPointerDown={this.onPointerDown}
+ onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
- // Todo: what is wheel? Are we talking about a mouse wheel?
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} >
- {/* so it appears that we are actually rendering the sections. Maybe this is what we're looking for? */}
+ onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}>
{this.renderedSections}
- {/* I think that showAddGroup must be passed in as false, which is why we can't find what Mehek showed
- Or it's because we aren't passing a pivot field */}
- {!this.showAddAGroup ? (null) :
- <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
- style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+ {!this.showAddAGroup ? null : (
+ <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
- </div>}
+ </div>
+ )}
{/* {this.chromeHidden || !this.props.isSelected() ? (null) :
<Switch
onChange={this.onToggle}
@@ -712,7 +744,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
</div>
</div>
</>
-
);
}
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index c191445e7..7b268cd49 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,28 +1,28 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { RichTextField } from "../../../fields/RichTextField";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { ScriptField } from "../../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { ImageField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, setupMoveUpEvents, returnFalse, returnEmptyString } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DragManager } from "../../util/DragManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { EditableView } from "../EditableView";
-import "./CollectionStackingView.scss";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
-import { Id } from "../../../fields/FieldSymbols";
-const higflyout = require("@hig/flyout");
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { RichTextField } from '../../../fields/RichTextField';
+import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { ScriptField } from '../../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, setupMoveUpEvents, returnFalse, returnEmptyString } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { EditableView } from '../EditableView';
+import './CollectionStackingView.scss';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { Id } from '../../../fields/FieldSymbols';
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -40,7 +40,7 @@ interface CSVFieldColumnProps {
columnWidth: number;
numGroupColumns: number;
gridGap: number;
- type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
+ type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
headings: () => object[];
// I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure
renderChildren: (docs: Doc[]) => JSX.Element[];
@@ -53,14 +53,15 @@ interface CSVFieldColumnProps {
@observer
export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
- @observable private _background = "inherit";
+ @observable private _background = 'inherit';
private dropDisposer?: DragManager.DragDropDisposer;
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
@observable _paletteOn = false;
@observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
- @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+ @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb';
+
_ele: HTMLElement | null = null;
// This is likely similar to what we will be doing. Why do we need to make these refs?
@@ -72,7 +73,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
this.props.observeHeight(ele);
this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
- }
+ };
+
componentWillUnmount() {
this.props.unobserveHeight(this._ele);
}
@@ -86,10 +88,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
getValue = (value: string): any => {
const parsed = parseInt(value);
if (!isNaN(parsed)) return parsed;
- if (value.toLowerCase().indexOf("true") > -1) return true;
- if (value.toLowerCase().indexOf("false") > -1) return false;
+ if (value.toLowerCase().indexOf('true') > -1) return true;
+ if (value.toLowerCase().indexOf('false') > -1) return false;
return value;
- }
+ };
@action
headingChanged = (value: string, shiftDown?: boolean) => {
@@ -98,7 +100,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
return false;
}
- this.props.docList.forEach(d => d[this.props.pivotField] = castedValue);
+ this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue));
if (this.props.headingObject) {
this.props.headingObject.setHeading(castedValue.toString());
this._heading = this.props.headingObject.heading;
@@ -106,17 +108,17 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
return true;
}
return false;
- }
+ };
@action
changeColumnColor = (color: string) => {
this.props.headingObject?.setColor(color);
this._color = color;
- }
+ };
- @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4");
- @action pointerLeave = () => this._background = "inherit";
- textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true);
+ @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = '#b4b4b4');
+ @action pointerLeave = () => (this._background = 'inherit');
+ textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
@@ -124,73 +126,78 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const key = this.props.pivotField;
const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _fitWidth: true, title: value, _autoHeight: true });
newDoc[key] = this.getValue(this.props.heading);
- const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+ const maxHeading = this.props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
newDoc.heading = heading;
FormattedTextBox.SelectOnLoad = newDoc[Id];
- FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " ";
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
return this.props.addDocument?.(newDoc) || false;
- }
+ };
@action
deleteColumn = () => {
- this.props.docList.forEach(d => d[this.props.pivotField] = undefined);
+ this.props.docList.forEach(d => (d[this.props.pivotField] = undefined));
if (this.props.columnHeaders && this.props.headingObject) {
const index = this.props.columnHeaders.indexOf(this.props.headingObject);
this.props.columnHeaders.splice(index, 1);
}
- }
+ };
@action
collapseSection = () => {
this.props.headingObject?.setCollapsed(!this.props.headingObject.collapsed);
this.toggleVisibility();
- }
+ };
headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
- //TODO: I think this is where I'm supposed to edit stuff
+ //TODO: I think this is where I'm supposed to edit stuff
startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
// is MakeAlias a way to make a copy of a doc without rendering it?
const alias = Doc.MakeAlias(this.props.Document);
alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1);
alias._pivotField = undefined;
let value = this.getValue(this._heading);
- value = typeof value === "string" ? `"${value}"` : value;
+ value = typeof value === 'string' ? `"${value}"` : value;
alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
if (alias.viewSpecScript) {
DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
return true;
}
return false;
- }
+ };
renderColorPicker = () => {
- const gray = "#f1efeb";
+ const gray = '#f1efeb';
const selected = this.props.headingObject ? this.props.headingObject.color : gray;
- const colors = ["pink2", "purple4", "bluegreen1", "yellow4", "gray", "red2", "bluegreen7", "bluegreen5", "orange1"];
- return <div className="collectionStackingView-colorPicker">
- <div className="colorOptions">
- {colors.map(col => {
- const palette = PastelSchemaPalette.get(col);
- return <div className={"colorPicker" + (selected === palette ? " active" : "")}
- style={{ backgroundColor: palette }} onClick={() => this.changeColumnColor(palette!)} />;
- })}
+ const colors = ['pink2', 'purple4', 'bluegreen1', 'yellow4', 'gray', 'red2', 'bluegreen7', 'bluegreen5', 'orange1'];
+ return (
+ <div className="collectionStackingView-colorPicker">
+ <div className="colorOptions">
+ {colors.map(col => {
+ const palette = PastelSchemaPalette.get(col);
+ return <div className={'colorPicker' + (selected === palette ? ' active' : '')} style={{ backgroundColor: palette }} onClick={() => this.changeColumnColor(palette!)} />;
+ })}
+ </div>
</div>
- </div>;
- }
+ );
+ };
renderMenu = () => {
- return <div className="collectionStackingView-optionPicker">
- <div className="optionOptions">
- <div className={"optionPicker" + (true ? " active" : "")} onClick={action(() => { })}>Add options here</div>
+ return (
+ <div className="collectionStackingView-optionPicker">
+ <div className="optionOptions">
+ <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
+ Add options here
+ </div>
+ </div>
</div>
- </div >;
- }
+ );
+ };
@observable private collapsed: boolean = false;
- private toggleVisibility = action(() => this.collapsed = !this.collapsed);
+ private toggleVisibility = action(() => (this.collapsed = !this.collapsed));
menuCallback = (x: number, y: number) => {
ContextMenu.Instance.clearItems();
@@ -198,42 +205,58 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const docItems: ContextMenuProps[] = [];
const dataDoc = this.props.DataDoc || this.props.Document;
- DocUtils.addDocumentCreatorMenuItems((doc) => {
- FormattedTextBox.SelectOnLoad = doc[Id];
- return this.props.addDocument?.(doc);
- }, this.props.addDocument, x, y, true);
+ DocUtils.addDocumentCreatorMenuItems(
+ doc => {
+ FormattedTextBox.SelectOnLoad = doc[Id];
+ return this.props.addDocument?.(doc);
+ },
+ this.props.addDocument,
+ x,
+ y,
+ true
+ );
- Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey =>
- docItems.push({
- description: ":" + fieldKey, event: () => {
- const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
- if (created) {
- if (this.props.Document.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+ if (created) {
+ if (this.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ }
+ return this.props.addDocument?.(created);
}
- return this.props.addDocument?.(created);
- }
- }, icon: "compress-arrows-alt"
- }));
- Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey =>
- docItems.push({
- description: ":" + fieldKey, event: () => {
- const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
- if (created) {
- const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
- if (container.isTemplateDoc) {
- Doc.MakeMetadataFieldTemplate(created, container);
- return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
+ if (created) {
+ const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+ if (container.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, container);
+ return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+ }
+ return this.props.addDocument?.(created) || false;
}
- return this.props.addDocument?.(created) || false;
- }
- }, icon: "compress-arrows-alt"
- }));
- !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" });
- !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" });
- ContextMenu.Instance.setDefaultItem("::", (name: string): void => {
- Doc.GetProto(this.props.Document)[name] = "";
- const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true });
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
+ !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
+ ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
+ Doc.GetProto(this.props.Document)[name] = '';
+ const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true });
if (created) {
if (this.props.Document.isTemplateDoc) {
Doc.MakeMetadataFieldTemplate(created, this.props.Document);
@@ -243,7 +266,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
});
const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y, undefined, true);
- }
+ };
+
@computed get innards() {
TraceMobx();
const key = this.props.pivotField;
@@ -251,39 +275,39 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const heading = this._heading;
const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin;
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
- const evContents = heading ? heading : this.props?.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
- const headingView = this.props.headingObject ?
- <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef}
+ const evContents = heading ? heading : this.props?.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
+ const headingView = this.props.headingObject ? (
+ <div
+ key={heading}
+ className="collectionStackingView-sectionHeader"
+ ref={this._headerRef}
style={{
marginTop: this.props.yMargin,
- width: (this.props.columnWidth) /
- ((uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1)) || 1)
+ width: this.props.columnWidth / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1),
}}>
- <div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div>
+ <div className={'collectionStackingView-collapseBar' + (this.props.headingObject.collapsed === true ? ' active' : '')} onClick={this.collapseSection}></div>
{/* the default bucket (no key value) has a tooltip that describes what it is.
Further, it does not have a color and cannot be deleted. */}
- <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
- title={evContents === `NO ${key.toUpperCase()} VALUE` ?
- `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
- style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit" }}>
- <EditableView
- GetValue={() => evContents}
- SetValue={this.headingChanged}
- contents={evContents}
- oneLine={true}
- toggle={this.toggleVisibility} />
- {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
+ <div
+ className="collectionStackingView-sectionHeader-subCont"
+ onPointerDown={this.headerDown}
+ title={evContents === `NO ${key.toUpperCase()} VALUE` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
+ style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : 'inherit' }}>
+ <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} toggle={this.toggleVisibility} />
+ {evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
<div className="collectionStackingView-sectionColor">
- <button className="collectionStackingView-sectionColorButton" onClick={action(e => this._paletteOn = !this._paletteOn)}>
+ <button className="collectionStackingView-sectionColorButton" onClick={action(e => (this._paletteOn = !this._paletteOn))}>
<FontAwesomeIcon icon="palette" size="lg" />
</button>
- {this._paletteOn ? this.renderColorPicker() : (null)}
+ {this._paletteOn ? this.renderColorPicker() : null}
</div>
+ )}
+ {
+ <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
+ <FontAwesomeIcon icon="trash" size="lg" />
+ </button>
}
- {<button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
- <FontAwesomeIcon icon="trash" size="lg" />
- </button>}
- {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
+ {evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
<div className="collectionStackingView-sectionOptions">
<Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
<button className="collectionStackingView-sectionOptionButton">
@@ -291,69 +315,76 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</button>
</Flyout>
</div>
- }
+ )}
</div>
- </div> : (null);
+ </div>
+ ) : null;
const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
const type = this.props.Document.type;
- return <>
- {this.props.Document._columnsHideIfEmpty ? (null) : headingView}
- {
- this.collapsed ? (null) :
+ return (
+ <>
+ {this.props.Document._columnsHideIfEmpty ? null : headingView}
+ {this.collapsed ? null : (
<div>
- <div key={`${heading}-stack`} className={`collectionStackingView-masonrySingle`}
+ <div
+ key={`${heading}-stack`}
+ className={`collectionStackingView-masonrySingle`}
style={{
padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
- margin: "auto",
- width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ margin: 'auto',
+ width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
- position: "relative",
+ position: 'relative',
gridGap: this.props.gridGap,
gridTemplateColumns: templatecols,
- gridAutoRows: "0px"
+ gridAutoRows: '0px',
}}>
{this.props.renderChildren(this.props.docList)}
</div>
- {!this.props.chromeHidden && type !== DocumentType.PRES ?
- // TODO: this is the "new" button: see what you can work with here
- // change cursor to pointer for this, and update dragging cursor
- //TODO: there is a bug that occurs when adding a freeform document and trying to move it around
- //TODO: would be great if there was additional space beyond the frame, so that you can actually see your
- // bottom note
- //TODO: ok, so we are using a single column, and this is it!
- <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
- style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10, marginLeft: 25 }}>
+ {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+ // TODO: this is the "new" button: see what you can work with here
+ // change cursor to pointer for this, and update dragging cursor
+ //TODO: there is a bug that occurs when adding a freeform document and trying to move it around
+ //TODO: would be great if there was additional space beyond the frame, so that you can actually see your
+ // bottom note
+ //TODO: ok, so we are using a single column, and this is it!
+ <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10, marginLeft: 25 }}>
<EditableView
GetValue={returnEmptyString}
SetValue={this.addNewTextDoc}
textCallback={this.textCallback}
placeholder={"Type ':' for commands"}
- contents={<FontAwesomeIcon icon={"plus"}/>}
+ contents={<FontAwesomeIcon icon={'plus'} />}
toggle={this.toggleVisibility}
- menuCallback={this.menuCallback}
+ menuCallback={this.menuCallback}
/>
- </div> : null}
+ </div>
+ ) : null}
</div>
- }
- </>;
+ )}
+ </>
+ );
}
-
render() {
TraceMobx();
const headings = this.props.headings();
const heading = this._heading;
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
return (
- <div className={"collectionStackingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
+ <div
+ className={'collectionStackingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
+ key={heading}
style={{
width: `${100 / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1)}%`,
height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
- background: this._background
+ background: this._background,
}}
- ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
+ ref={this.createColumnDropRef}
+ onPointerEnter={this.pointerEntered}
+ onPointerLeave={this.pointerLeave}>
{this.innards}
- </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 42e157396..5479929bd 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,24 +1,23 @@
-import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx";
-import CursorField from "../../../fields/CursorField";
-import { Doc, Opt, Field, DocListCast, AclPrivate, StrListCast } from "../../../fields/Doc";
-import { Id, ToString } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-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 { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { ImageUtils } from "../../util/Import & Export/ImageUtils";
-import { InteractionUtils } from "../../util/InteractionUtils";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocComponent } from "../DocComponent";
-import React = require("react");
+import { action, computed, observable } from 'mobx';
import ReactLoading from 'react-loading';
import * as rp from 'request-promise';
-import { Networking } from "../../Network";
-
+import CursorField from '../../../fields/CursorField';
+import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { ScriptField } from '../../../fields/ScriptField';
+import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { returnFalse, Utils } from '../../../Utils';
+import { DocServer } from '../../DocServer';
+import { Networking } from '../../Network';
+import { ImageUtils } from '../../util/Import & Export/ImageUtils';
+import { InteractionUtils } from '../../util/InteractionUtils';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { DocComponent } from '../DocComponent';
+import React = require('react');
export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
@@ -33,7 +32,8 @@ export function CollectionSubView<X>(moreProps?: X) {
protected _mainCont?: HTMLDivElement;
@observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it
@observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it
- protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view
+ protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+ //used for stacking and masonry view
this.dropDisposer?.();
this.gestureDisposer?.();
this._multiTouchDisposer?.();
@@ -43,8 +43,9 @@ export function CollectionSubView<X>(moreProps?: X) {
this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
}
- }
- protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
+ };
+ protected CreateDropTarget(ele: HTMLDivElement) {
+ //used in schema view
this.createDashEventsTarget(ele);
}
@@ -54,13 +55,12 @@ export function CollectionSubView<X>(moreProps?: X) {
}
@computed get dataDoc() {
- return (this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
- this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+ return this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
}
rootSelected = (outsideReaction?: boolean) => {
return this.props.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected(outsideReaction));
- }
+ };
// The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
// When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
@@ -73,10 +73,12 @@ export function CollectionSubView<X>(moreProps?: X) {
return this.dataDoc[this.props.fieldKey];
}
- get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
+ get childLayoutPairs(): { layout: Doc; data: Doc }[] {
const { Document, DataDoc } = this.props;
- const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc)).
- filter(pair => { // filter out any documents that have a proto that we don't have permissions to
+ const validPairs = this.childDocs
+ .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc))
+ .filter(pair => {
+ // filter out any documents that have a proto that we don't have permissions to
return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
});
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
@@ -85,21 +87,24 @@ export function CollectionSubView<X>(moreProps?: X) {
return Cast(this.dataField, listSpec(Doc));
}
collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters);
- collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
+ collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec('string'), []);
childDocFilters = () => [...(this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
unrecursiveDocFilters = () => [...(this.props.docFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()];
- IsFiltered = () => this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
- this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined
+ IsFiltered = () =>
+ this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined;
searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
@computed.struct get childDocs() {
TraceMobx();
let rawdocs: (Doc | Promise<Doc>)[] = [];
- if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
+ if (this.dataField instanceof Doc) {
+ // if collection data is just a document, then promote it to a singleton list;
rawdocs = [this.dataField];
- } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it.
+ } else if (Cast(this.dataField, listSpec(Doc), null)) {
+ // otherwise, if the collection data is a list, then use it.
rawdocs = Cast(this.dataField, listSpec(Doc), null);
- } else { // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
+ } else {
+ // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
// For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
// Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image.
const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
@@ -117,19 +122,19 @@ export function CollectionSubView<X>(moreProps?: X) {
return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
}
- // console.log(CurrentUserUtils.ActiveDashboard._docFilters);
+ // console.log(Doc.ActiveDashboard._docFilters);
// if (!this.props.Document._docFilters && this.props.Document.currentFilter) {
// (this.props.Document.currentFilter as Doc).filterBoolean = (this.props.ContainingCollectionDoc?.currentFilter as Doc)?.filterBoolean;
// }
const docsforFilter: Doc[] = [];
- childDocs.forEach((d) => {
+ childDocs.forEach(d => {
// if (DocUtils.Excluded(d, docFilters)) return;
- let notFiltered = d.z || Doc.IsSystem(d) || (DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0);
+ let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0;
if (notFiltered) {
- notFiltered = ((!searchDocs.length || searchDocs.includes(d)) && (DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0));
+ notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0;
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
- const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const data = d[annos ? fieldKey + '-annotations' : fieldKey];
if (data !== undefined) {
let subDocs = DocListCast(data);
if (subDocs.length > 0) {
@@ -137,11 +142,12 @@ export function CollectionSubView<X>(moreProps?: X) {
notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, viewSpecScript, d).length);
while (subDocs.length > 0 && !notFiltered) {
newarray = [];
- subDocs.forEach((t) => {
+ subDocs.forEach(t => {
const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView");
- notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length));
- DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc));
+ const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
+ notFiltered =
+ notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length));
+ DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
});
subDocs = newarray;
}
@@ -157,7 +163,7 @@ export function CollectionSubView<X>(moreProps?: X) {
protected async setCursorPosition(position: [number, number]) {
let ind;
const doc = this.props.Document;
- const id = CurrentUserUtils.id;
+ const id = Doc.UserDoc()[Id];
const email = Doc.CurrentUserEmail;
const pos = { x: position[0], y: position[1] };
if (id && email) {
@@ -167,7 +173,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
// The following conditional detects a recurring bug we've seen on the server
if (proto[Id] === Docs.Prototypes.get(DocumentType.COL)[Id]) {
- alert("COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...");
+ alert('COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...');
console.log(doc);
console.log(proto);
throw new Error(`AHA! You were trying to set a cursor on a collection's proto, which is the original collection proto! Look at the two previously printed lines for document values!`);
@@ -186,8 +192,7 @@ export function CollectionSubView<X>(moreProps?: X) {
}
@undoBatch
- protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
- }
+ protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
if (de.complete.docDragData) {
@@ -210,12 +215,16 @@ export function CollectionSubView<X>(moreProps?: X) {
const dropAction = docDragData.dropAction || docDragData.userDropAction;
const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
const someMoved = !docDragData.userDropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
- if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop);
- if ((!dropAction || dropAction === "same" || dropAction === "move" || someMoved) && docDragData.moveDocument) {
+ if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
+ if ((!dropAction || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
if (movedDocs.length) {
- const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || (!this.props.isAnnotationOverlay || this.props.Document.allowOverlayDrop) ||
+ const canAdd =
+ this.props.Document._viewType === CollectionViewType.Pile ||
+ de.embedKey ||
+ !this.props.isAnnotationOverlay ||
+ this.props.Document.allowOverlayDrop ||
Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse);
} else {
@@ -228,11 +237,10 @@ export function CollectionSubView<X>(moreProps?: X) {
ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
added = this.addDocument(docDragData.droppedDocuments);
}
- !added && alert("You cannot perform this move");
+ !added && alert('You cannot perform this move');
e.stopPropagation();
return added;
- }
- else if (de.complete.annoDragData) {
+ } else if (de.complete.annoDragData) {
const dropCreator = de.complete.annoDragData.dropDocCreator;
de.complete.annoDragData.dropDocCreator = () => {
const dropped = dropCreator(this.props.isAnnotationOverlay ? this.rootDoc : undefined);
@@ -253,11 +261,11 @@ export function CollectionSubView<X>(moreProps?: X) {
}
const { dataTransfer } = e;
- const html = dataTransfer.getData("text/html");
- const text = dataTransfer.getData("text/plain");
- const uriList = dataTransfer.getData("text/uri-list");
+ const html = dataTransfer.getData('text/html');
+ const text = dataTransfer.getData('text/plain');
+ const uriList = dataTransfer.getData('text/uri-list');
- if (text && text.startsWith("<div")) {
+ if (text && text.startsWith('<div')) {
return;
}
@@ -271,11 +279,15 @@ export function CollectionSubView<X>(moreProps?: X) {
const href = FormattedTextBox.GetHref(html);
if (href) {
const docid = FormattedTextBox.GetDocFromUrl(href);
- if (docid) { // prosemirror text containing link to dash document
+ if (docid) {
+ // prosemirror text containing link to dash document
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);
+ if (options.x || options.y) {
+ f.x = options.x as number;
+ f.y = options.y as number;
+ } // should be in CollectionFreeFormView
+ f instanceof Doc && addDocument(f);
}
});
} else {
@@ -286,58 +298,53 @@ export function CollectionSubView<X>(moreProps?: X) {
}
return;
}
- if (!html.startsWith("<a")) {
- const tags = html.split("<");
- if (tags[0] === "") tags.splice(0, 1);
- let img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
- const cors = img.includes("corsProxy") ? img.match(/http.*corsProxy\//)![0] : "";
- img = cors ? img.replace(cors, "") : img;
+ if (!html.startsWith('<a')) {
+ const tags = html.split('<');
+ if (tags[0] === '') tags.splice(0, 1);
+ let img = tags[0].startsWith('img') ? tags[0] : tags.length > 1 && tags[1].startsWith('img') ? tags[1] : '';
+ const cors = img.includes('corsProxy') ? img.match(/http.*corsProxy\//)![0] : '';
+ img = cors ? img.replace(cors, '') : img;
if (img) {
- const split = img.split("src=\"")[1].split("\"")[0];
+ const split = img.split('src="')[1].split('"')[0];
let source = split;
- if (split.startsWith("data:image") && split.includes("base64")) {
- const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
+ if (split.startsWith('data:image') && split.includes('base64')) {
+ const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [split] });
source = Utils.prepend(accessPaths.agnostic.client);
}
- if (source.startsWith("http")) {
+ if (source.startsWith('http')) {
const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
ImageUtils.ExtractExif(doc);
addDocument(doc);
}
return;
} else {
- const path = window.location.origin + "/doc/";
+ const path = window.location.origin + '/doc/';
if (text.startsWith(path)) {
- const docid = text.replace(Doc.globalServerPath(), "").split("?")[0];
+ const docid = text.replace(Doc.globalServerPath(), '').split('?')[0];
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);
+ if (options.x || options.y) {
+ f.x = options.x as number;
+ f.y = options.y as number;
+ } // should be in CollectionFreeFormView
+ f instanceof Doc && addDocument(f);
}
});
} else {
- const srcWeb = SelectionManager.Docs().lastElement();
- const srcUrl = (srcWeb?.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
- const reg = new RegExp(Utils.prepend(""), "g");
+ const srcWeb = SelectionManager.Views().lastElement();
+ const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
+ const reg = new RegExp(Utils.prepend(''), 'g');
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
- const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
- Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text;
+ const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0];
+ const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor });
+ Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
addDocument(htmlDoc);
if (srcWeb) {
- const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName("iframe")?.[0];
- const focusNode = (iframe?.contentDocument?.getSelection()?.focusNode as any);
+ const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
+ const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
if (focusNode) {
- const rects = iframe?.contentWindow?.getSelection()?.getRangeAt(0).getClientRects();
- "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
- const x = (rects && Array.from(rects).reduce((x: any, r: DOMRect) => x === undefined || r.x < x ? r.x : x, undefined as any)) || 0;
- const y = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y < y ? r.y : y, undefined as any)) || 0);
- const r = (rects && Array.from(rects).reduce((x: any, r: DOMRect) => x === undefined || r.x + r.width > x ? r.x + r.width : x, undefined as any)) || 0;
- const b = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y + r.height > y ? r.y + r.height : y, undefined as any)) || 0);
- const anchor = Docs.Create.FreeformDocument([], { backgroundColor: "transparent", _width: r - x, _height: b - y, x, y, annotationOn: srcWeb });
- anchor.context = srcWeb;
- const key = Doc.LayoutFieldKey(srcWeb);
- Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
- DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+ const anchor = srcWeb?.ComponentView?.getAnchor?.();
+ anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
}
}
}
@@ -347,11 +354,10 @@ export function CollectionSubView<X>(moreProps?: X) {
}
if (uriList || text) {
- if ((uriList || text).includes("www.youtube.com/watch") || text.includes("www.youtube.com/embed")) {
-
- const batch = UndoManager.StartBatch("youtube upload");
+ if ((uriList || text).includes('www.youtube.com/watch') || text.includes('www.youtube.com/embed')) {
+ const batch = UndoManager.StartBatch('youtube upload');
const generatedDocuments: Doc[] = [];
- this.slowLoadDocuments((uriList || text).split("v=")[1].split("&")[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
+ this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
return;
}
@@ -382,15 +388,16 @@ export function CollectionSubView<X>(moreProps?: X) {
// alias._height = 512;
// alias._width = 400;
// addDocument(alias);
- // } else
+ // } else
{
- const newDoc = Docs.Create.WebDocument(uriList.split("#annotations:")[0], {// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
+ const newDoc = Docs.Create.WebDocument(uriList.split('#annotations:')[0], {
+ // clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
...options,
- title: uriList.split("#annotations:")[0],
+ title: uriList.split('#annotations:')[0],
_width: 400,
_height: 512,
_nativeWidth: 850,
- useCors: true
+ useCors: true,
});
addDocument(newDoc);
}
@@ -402,83 +409,99 @@ export function CollectionSubView<X>(moreProps?: X) {
const files: File[] = [];
const generatedDocuments: Doc[] = [];
if (!length) {
- alert("No uploadable content found.");
+ alert('No uploadable content found.');
return;
}
- const batch = UndoManager.StartBatch("collection view drop");
+ const batch = UndoManager.StartBatch('collection view drop');
for (let i = 0; i < length; i++) {
const item = e.dataTransfer.items[i];
- if (item.kind === "string" && item.type.includes("uri")) {
+ if (item.kind === 'string' && item.type.includes('uri')) {
const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
- const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
+ const type = (await rp.head(Utils.CorsProxy(stringContents)))['content-type'];
if (type) {
const doc = await DocUtils.DocumentFromType(type, Utils.CorsProxy(stringContents), options);
doc && generatedDocuments.push(doc);
}
}
- if (item.kind === "file") {
+ if (item.kind === 'file') {
const file = item.getAsFile();
file?.type && files.push(file);
- file?.type === "application/json" && Utils.readUploadedFileAsText(file).then(result => {
- const json = JSON.parse(result as string);
- 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);
- proto._width = 120;
- proto._height = 20;
- return proto;
- }
- ), { _width: 150, _height: 600, title: "across", backgroundColor: "white", _singleLine: true }));
- });
+ file?.type === 'application/json' &&
+ Utils.readUploadedFileAsText(file).then(result => {
+ const json = JSON.parse(result as string);
+ 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);
+ proto._width = 120;
+ proto._height = 20;
+ return proto;
+ }),
+ { _width: 150, _height: 600, title: 'across', backgroundColor: 'white', _singleLine: true }
+ )
+ );
+ });
}
}
this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
}
- slowLoadDocuments = async (files: (File[] | string), options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, clientX: number, clientY: number, addDocument: (doc: Doc | Doc[]) => boolean) => {
- const disposer = OverlayView.Instance.addElement(
- <ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 });
- if (typeof files === "string") {
- generatedDocuments.push(...await DocUtils.uploadYoutubeVideo(files, options));
+ slowLoadDocuments = async (
+ files: File[] | string,
+ options: DocumentOptions,
+ generatedDocuments: Doc[],
+ text: string,
+ completed: ((doc: Doc[]) => void) | undefined,
+ clientX: number,
+ clientY: number,
+ addDocument: (doc: Doc | Doc[]) => boolean
+ ) => {
+ const disposer = OverlayView.Instance.addElement(<ReactLoading type={'spinningBubbles'} color={'green'} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 });
+ if (typeof files === 'string') {
+ generatedDocuments.push(...(await DocUtils.uploadYoutubeVideo(files, options)));
} else {
- generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options));
+ generatedDocuments.push(...(await DocUtils.uploadFilesToDocs(files, options)));
}
if (generatedDocuments.length) {
// Creating a dash document
const isFreeformView = this.props.Document._viewType === CollectionViewType.Freeform;
- const set = !isFreeformView ? generatedDocuments :
- generatedDocuments.length > 1 ? generatedDocuments.map(d => { DocUtils.iconify(d); return d; }) : [];
+ const set = !isFreeformView
+ ? generatedDocuments
+ : generatedDocuments.length > 1
+ ? generatedDocuments.map(d => {
+ DocUtils.iconify(d);
+ return d;
+ })
+ : [];
if (completed) completed(set);
else {
if (isFreeformView && generatedDocuments.length > 1) {
- addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!));
+ addDocument(DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!);
} else {
generatedDocuments.forEach(addDocument);
}
}
} else {
- if (text && !text.includes("https://")) {
+ if (text && !text.includes('https://')) {
addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
} else {
- alert("Document upload failed - possibly an unsupported file type.");
+ alert('Document upload failed - possibly an unsupported file type.');
}
}
disposer();
- }
+ };
}
return CollectionSubView;
}
-import { DragManager, dropActionType } from "../../util/DragManager";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
-import { CollectionView, CollectionViewType, CollectionViewProps } from "./CollectionView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { OverlayView } from "../OverlayView";
-import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
-
+import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
+import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DragManager, dropActionType } from '../../util/DragManager';
+import { SelectionManager } from '../../util/SelectionManager';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { OverlayView } from '../OverlayView';
+import { CollectionView, CollectionViewProps } from './CollectionView';
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index d6398fda5..3dd9d2d84 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -10,6 +10,7 @@ import { ComputedField, ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { ScriptingGlobals } from "../../util/ScriptingGlobals";
import { ContextMenu } from "../ContextMenu";
@@ -58,7 +59,7 @@ export class CollectionTimeView extends CollectionSubView() {
//const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
runInAction(() => {
- this._childClickedScript = ScriptField.MakeScript("openInLightbox(self, shiftKey)", { this: Doc.name, shiftKey: "boolean" });//, { detailView: detailView! });
+ this._childClickedScript = ScriptField.MakeScript("openInLightbox(self)", { this: Doc.name });
this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
});
}
@@ -128,15 +129,16 @@ export class CollectionTimeView extends CollectionSubView() {
}
}
+ dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF;
@computed get contents() {
return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }}
onClick={this.contentsDown}>
<CollectionFreeFormView {...this.props}
engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
- fitContentsToDoc={returnTrue}
+ fitContentsToBox={returnTrue}
childClickScript={this._childClickedScript}
viewDefDivClick={this._viewDefDivClick}
- childFreezeDimensions={true}
+ //dontScaleFilter={this.dontScaleFilter}
layoutEngine={this.layoutEngine} />
</div>;
}
@@ -174,7 +176,7 @@ export class CollectionTimeView extends CollectionSubView() {
typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
Array.from(keySet).map(fieldKey =>
docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
- docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
+ docItems.push({ description: ":default", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y, ":");
@@ -234,20 +236,30 @@ export class CollectionTimeView extends CollectionSubView() {
}
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+ const pivotField = StrCast(pivotDoc._pivotField) || "author";
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
+ const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
- pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField;
+ pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotField;
pivotDoc._prevFilterIndex = ++prevFilterIndex;
- runInAction(() => {
- pivotDoc._docFilters = new List();
+ pivotDoc._docFilters = new List();
+ setTimeout(action(() => {
const filterVals = (bounds.payload as string[]);
- filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+ filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, "check"));
const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
pivotDoc._pivotField = filterVals[0];
}
}
- });
+ const newFilters = StrListCast(pivotDoc._docFilters);
+ if (newFilters.length && originalFilter.length &&
+ newFilters.lastElement() === originalFilter.lastElement()) {
+ pivotDoc._prevFilterIndex = --prevFilterIndex;
+ pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined;
+ pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined;
+ pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined;
+ }
+ }));
}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index b664d9d82..93523a6cf 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,7 +1,9 @@
@import "../global/globalCssVariables";
+
.collectionTreeView-container {
transform-origin: top left;
+ height: 100%;
}
.collectionTreeView-dropTarget {
border-width: $COLLECTION_BORDER_WIDTH;
@@ -30,9 +32,11 @@
width: unset;
height: unset;
}
+ &:hover {
+ cursor: ns-resize;
+ }
}
-
.no-indent {
padding-left: 0;
width: max-content;
@@ -71,6 +75,11 @@
display: none;
}
+.collectionTreeView-contents {
+ display: flex;
+ flex-direction: column;
+}
+
.collectionTreeView-titleBar {
display: inline-block;
width: 100%;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e84517f40..dce792d19 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,5 +1,5 @@
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -9,36 +9,46 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from "../../util/DragManager";
+import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { EditableView } from "../EditableView";
+import { EditableView } from '../EditableView';
import { DocumentView } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { StyleProp } from '../StyleProvider';
import { CollectionFreeFormView } from './collectionFreeForm';
-import { CollectionSubView } from "./CollectionSubView";
-import "./CollectionTreeView.scss";
-import { TreeView } from "./TreeView";
-import React = require("react");
-const _global = (window /* browser */ || global /* node */) as any;
+import { CollectionSubView } from './CollectionSubView';
+import './CollectionTreeView.scss';
+import { TreeView } from './TreeView';
+import React = require('react');
+const _global = (window /* browser */ || global) /* node */ as any;
export type collectionTreeViewProps = {
- treeViewExpandedView?: "fields" | "layout" | "links" | "data";
+ treeViewExpandedView?: 'fields' | 'layout' | 'links' | 'data';
treeViewOpen?: boolean;
treeViewHideTitle?: boolean;
treeViewHideHeaderFields?: boolean;
treeViewSkipFields?: string[]; // prevents specific fields from being displayed (see LinkBox)
onCheckedClick?: () => ScriptField;
onChildClick?: () => ScriptField;
+ // TODO: [AL] add these fields
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ hierarchyIndex?: number[];
};
+export enum TreeViewType {
+ outline = 'outline',
+ fileSystem = 'fileSystem',
+ default = 'default',
+}
+
@observer
export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() {
private _treedropDisposer?: DragManager.DragDropDisposer;
@@ -50,13 +60,28 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
private observer: any; // observer for monitoring tree view items.
private static expandViewLabelSize = 20;
- @computed get doc() { return this.props.Document; }
- @computed get dataDoc() { return this.props.DataDoc || this.doc; }
- @computed get treeViewtruncateTitleWidth() { return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth()); }
- @computed get treeChildren() { TraceMobx(); return this.props.childDocuments || this.childDocs; }
- @computed get outlineMode() { return this.doc.treeViewType === "outline"; }
- @computed get fileSysMode() { return this.doc.treeViewType === "fileSystem"; }
- @computed get dashboardMode() { return this.doc === Doc.UserDoc().myDashboards; }
+ @computed get doc() {
+ return this.props.Document;
+ }
+ @computed get dataDoc() {
+ return this.props.DataDoc || this.doc;
+ }
+ @computed get treeViewtruncateTitleWidth() {
+ return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth());
+ }
+ @computed get treeChildren() {
+ TraceMobx();
+ return this.props.childDocuments || this.childDocs;
+ }
+ @computed get outlineMode() {
+ return this.doc.treeViewType === TreeViewType.outline;
+ }
+ @computed get fileSysMode() {
+ return this.doc.treeViewType === TreeViewType.fileSystem;
+ }
+ @computed get dashboardMode() {
+ return this.doc === Doc.MyDashboards;
+ }
@observable _explainerHeight = 0; // height of the description of the tree view
@@ -64,11 +89,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
// these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
@observable _isAnyChildContentActive = false;
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
- isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None ||
- (this.props.isContentActive?.() || this.props.Document.forceActive ||
- this.props.isSelected(outsideReaction) || this._isAnyChildContentActive ||
- this.props.rootSelected(outsideReaction)) ? true : false)
+ whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ isContentActive = (outsideReaction?: boolean) =>
+ Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) ? true : false;
componentWillUnmount() {
this._isDisposing = true;
@@ -78,47 +101,51 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
componentDidMount() {
- this._disposers.autoheight = reaction(() => this.rootDoc.autoHeight,
+ this._disposers.autoheight = reaction(
+ () => this.rootDoc.autoHeight,
auto => auto && this.computeHeight(),
- { fireImmediately: true });
+ { fireImmediately: true }
+ );
}
computeHeight = () => {
if (!this._isDisposing) {
- const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", ""));
- const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot());
+ const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', ''));
+ const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot());
this.layoutDoc._autoHeightMargins = bodyHeight;
- this.props.setHeight(bodyHeight + titleHeight);
+ this.props.setHeight?.(bodyHeight + titleHeight);
}
- }
+ };
unobserveHeight = (ref: any) => {
this.refList.delete(ref);
this.rootDoc.autoHeight && this.computeHeight();
- }
+ };
observeHeight = (ref: any) => {
if (ref) {
this.refList.add(ref);
- this.observer = new _global.ResizeObserver(action((entries: any) => {
- if (this.rootDoc.autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) {
- this.computeHeight();
- }
- }));
+ this.observer = new _global.ResizeObserver(
+ action((entries: any) => {
+ if (this.rootDoc.autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) {
+ this.computeHeight();
+ }
+ })
+ );
this.rootDoc.autoHeight && this.computeHeight();
this.observer.observe(ref);
}
- }
+ };
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- if (this._mainEle = ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
- }
+ if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
+ };
protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData) {
const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d));
- dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction;
+ dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? 'same' : dragData.dropAction;
}
- }
+ };
@action
remove = (doc: Doc | Doc[]): boolean => {
@@ -138,7 +165,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return true;
}
return false;
- }
+ };
@action
addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
@@ -150,73 +177,85 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}, true);
if (this.doc.resolvedDataDoc instanceof Promise) return false;
return relativeTo === undefined ? this.props.addDocument?.(docs) || false : doAddDoc(docs);
- }
+ };
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!Doc.UserDoc().noviceMode) {
+ if (!Doc.noviceMode) {
const layoutItems: ContextMenuProps[] = [];
- layoutItems.push({ description: "Make tree state " + (this.doc.treeViewOpenIsTransient ? "persistent" : "transient"), event: () => this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient, icon: "paint-brush" });
- layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" });
- layoutItems.push({ description: (this.doc.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle, icon: "paint-brush" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
- const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
- const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- onClicks.push({ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" });
- !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
+ layoutItems.push({ description: 'Make tree state ' + (this.doc.treeViewOpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient), icon: 'paint-brush' });
+ layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields), icon: 'paint-brush' });
+ layoutItems.push({ description: (this.doc.treeViewHideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle), icon: 'paint-brush' });
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
+ const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...');
+ const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
}
- }
+ };
onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs);
@undoBatch
makeTextCollection = (childDocs: Doc[]) => {
this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true);
- }
+ };
get editableTitle() {
- return <EditableView
- contents={this.dataDoc.title}
- display={"block"}
- maxHeight={72}
- height={"auto"}
- GetValue={() => StrCast(this.dataDoc.title)}
- SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
- if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(this.treeChildren);
- this.dataDoc.title = value;
- return true;
- })} />;
+ return (
+ <EditableView
+ contents={this.dataDoc.title}
+ display={'block'}
+ maxHeight={72}
+ height={'auto'}
+ GetValue={() => StrCast(this.dataDoc.title)}
+ SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
+ if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
+ this.dataDoc.title = value;
+ return true;
+ })}
+ />
+ );
}
-
+ onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ if (this.outlineMode && e.key === 'Enter') {
+ e.stopPropagation();
+ this.makeTextCollection(this.treeChildren);
+ return true;
+ }
+ };
get documentTitle() {
- return <FormattedTextBox
- {...this.props}
- fieldKey={"text"}
- renderDepth={this.props.renderDepth + 1}
- isContentActive={this.isContentActive}
- isDocumentActive={this.isContentActive}
- rootSelected={returnTrue}
- forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not autoHeight
- PanelWidth={this.documentTitleWidth}
- PanelHeight={this.documentTitleHeight}
- scaling={returnOne}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.doc}
- ContainingCollectionView={this.props.CollectionView}
- addDocument={returnFalse}
- moveDocument={returnFalse}
- removeDocument={returnFalse}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- bringToFront={returnFalse}
- />;
+ return (
+ <FormattedTextBox
+ {...this.props}
+ fieldKey={'text'}
+ renderDepth={this.props.renderDepth + 1}
+ isContentActive={this.isContentActive}
+ isDocumentActive={this.isContentActive}
+ rootSelected={returnTrue}
+ forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not autoHeight
+ PanelWidth={this.documentTitleWidth}
+ PanelHeight={this.documentTitleHeight}
+ NativeDimScaling={returnOne}
+ onKey={this.onKey}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={this.doc}
+ ContainingCollectionView={this.props.CollectionView}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ bringToFront={returnFalse}
+ />
+ );
}
childContextMenuItems = () => {
const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
const icons = StrListCast(this.doc.childContextMenuIcons);
return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
- }
+ };
@computed get treeViewElements() {
TraceMobx();
const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
@@ -247,38 +286,34 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this.props.treeViewSkipFields,
true,
this.whenChildContentsActiveChanged,
- this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null),
+ this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, 'boolean', null),
this.observeHeight,
this.unobserveHeight,
- this.childContextMenuItems()
+ this.childContextMenuItems(),
+ //TODO: [AL] add these
+ this.props.AddToMap,
+ this.props.RemFromMap,
+ this.props.hierarchyIndex
);
}
@computed get titleBar() {
- return this.dataDoc === null ? (null) :
- <div className="collectionTreeView-titleBar" key={this.doc[Id]}
- style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}}
- ref={r => this._titleRef = r}
- onKeyDown={e => {
- if (this.outlineMode) {
- e.stopPropagation();
- e.key === "Enter" && this.makeTextCollection(this.treeChildren);
- }
- }}>
+ return this.dataDoc === null ? null : (
+ <div className="collectionTreeView-titleBar" key={this.doc[Id]} style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}} ref={r => (this._titleRef = r)}>
{this.outlineMode ? this.documentTitle : this.editableTitle}
- </div>;
+ </div>
+ );
}
@computed get noviceExplainer() {
- return !Doc.UserDoc().noviceMode || !this.rootDoc.explainer ? (null) :
- <div className="documentExplanation"> {this.rootDoc.explainer} </div>;
+ return !Doc.noviceMode || !this.rootDoc.explainer ? null : <div className="documentExplanation"> {StrCast(this.rootDoc.explainer)} </div>;
}
return35 = () => 35;
@computed get buttonMenu() {
const menuDoc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
// To create a multibutton menu add a CollectionLinearView
- return !menuDoc ? null :
- (<div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
+ return !menuDoc ? null : (
+ <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
<DocumentView
Document={menuDoc}
DataDoc={menuDoc}
@@ -296,7 +331,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -306,13 +340,19 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
/>
- </div>);
+ </div>
+ );
}
- @computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); }
- @computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); }
+ @computed get nativeWidth() {
+ return Doc.NativeWidth(this.Document, undefined, true);
+ }
+ @computed get nativeHeight() {
+ return Doc.NativeHeight(this.Document, undefined, true);
+ }
- @computed get contentScaling() {
+ /// scale factor for tree view so that it will fit within it's panel bounds
+ @computed get nativeDimScaling() {
const nw = this.nativeWidth;
const nh = this.nativeHeight;
const hscale = nh ? this.props.PanelHeight() / nh : 1;
@@ -326,74 +366,78 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins);
truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
- panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.scaling?.() || 1);
+ panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.NativeDimScaling?.() || 1);
addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
- this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false
+ this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
contentFunc = () => {
const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
- const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined;
- const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : this.titleBar;
+ const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined);
+ const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar;
return [
- <div className="collectionTreeView-contents" key="tree" style={{
- ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
- overflow: "auto",
- height: this.layoutDoc._autoHeight ? "max-content" : "100%"
- }} >
+ <div
+ className="collectionTreeView-contents"
+ key="tree"
+ style={{
+ ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
+ overflow: 'auto',
+ height: '100%', //this.layoutDoc._autoHeight ? "max-content" : "100%"
+ }}>
{titleBar}
- <div className="collectionTreeView-container"
+ <div
+ className="collectionTreeView-container"
style={{
- transform: this.outlineMode ? `scale(${this.contentScaling})` : "",
+ transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '',
paddingLeft: `${this.marginX()}px`,
- height: "max-content",
- width: this.outlineMode ? `calc(${100 / this.contentScaling}%)` : ""
+ width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '',
}}
onContextMenu={this.onContextMenu}>
- {!this.buttonMenu && !this.noviceExplainer ? (null) :
+ {!this.buttonMenu && !this.noviceExplainer ? null : (
<div className="documentButtonMenu" ref={action((r: HTMLDivElement) => r && (this._explainerHeight = r.getBoundingClientRect().height))}>
{this.buttonMenu}
{this.noviceExplainer}
</div>
- }
- <div className="collectionTreeView-dropTarget"
+ )}
+ <div
+ className="collectionTreeView-dropTarget"
style={{
background: background(),
height: `calc(100% - ${this._explainerHeight}px)`,
- pointerEvents: pointerEvents()
+ pointerEvents: pointerEvents(),
}}
onWheel={e => e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}>
- <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
- {this.treeViewElements}
- </ul>
- </div >
+ <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
+ </div>
</div>
- </div>
+ </div>,
];
- }
+ };
render() {
TraceMobx();
- return !(this.doc instanceof Doc) || !this.treeChildren ? (null) :
- this.doc.treeViewHasOverlay ?
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- isAnnotationOverlay={true}
- isAnnotationOverlayScrollable={true}
- childDocumentsActive={this.props.isDocumentActive}
- fieldKey={this.props.fieldKey + "-annotations"}
- dropAction={"move"}
- select={emptyFunction}
- addDocument={this.addAnnotationDocument}
- removeDocument={this.remAnnotationDocument}
- moveDocument={this.moveAnnotationDocument}
- bringToFront={emptyFunction}
- renderDepth={this.props.renderDepth + 1} >
- {this.contentFunc}
- </CollectionFreeFormView> :
- this.contentFunc();
+ return !(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? (
+ <CollectionFreeFormView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ isAnnotationOverlay={true}
+ isAnnotationOverlayScrollable={true}
+ childDocumentsActive={this.props.isDocumentActive}
+ fieldKey={this.props.fieldKey + '-annotations'}
+ dropAction={'move'}
+ select={emptyFunction}
+ addDocument={this.addAnnotationDocument}
+ removeDocument={this.remAnnotationDocument}
+ moveDocument={this.moveAnnotationDocument}
+ bringToFront={emptyFunction}
+ renderDepth={this.props.renderDepth + 1}>
+ {this.contentFunc}
+ </CollectionFreeFormView>
+ ) : (
+ this.contentFunc()
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 54b0a2af3..1ee77d4ce 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,26 +1,26 @@
import { computed, observable, runInAction } from 'mobx';
-import { observer } from "mobx-react";
+import { observer } from 'mobx-react';
import * as React from 'react';
-import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { Doc, DocListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { ObjectField } from '../../../fields/ObjectField';
import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { returnEmptyString } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
+import { CollectionViewType } from '../../documents/DocumentTypes';
import { BranchCreate, BranchTask } from '../../documents/Gitlike';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { InteractionUtils } from '../../util/InteractionUtils';
-import { ContextMenu } from "../ContextMenu";
+import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
+import { DashboardView } from '../DashboardView';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
import { CollectionCarouselView } from './CollectionCarouselView';
-import { CollectionDockingView } from "./CollectionDockingView";
+import { CollectionDockingView } from './CollectionDockingView';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionGridView } from './collectionGrid/CollectionGridView';
import { CollectionLinearView } from './collectionLinear';
@@ -28,73 +28,58 @@ import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMul
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
import { CollectionNoteTakingView } from './CollectionNoteTakingView';
import { CollectionPileView } from './CollectionPileView';
-import { CollectionSchemaView } from "./collectionSchema/CollectionSchemaView";
+import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
import { CollectionStackingView } from './CollectionStackingView';
import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
-import { CollectionTreeView } from "./CollectionTreeView";
+import { CollectionTreeView } from './CollectionTreeView';
import './CollectionView.scss';
export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
-//TODO: see everywhere that this exists
-export enum CollectionViewType {
- Invalid = "invalid",
- Freeform = "freeform",
- Schema = "schema",
- Docking = "docking",
- Tree = 'tree',
- Stacking = "stacking",
- Masonry = "masonry",
- Multicolumn = "multicolumn",
- Multirow = "multirow",
- Time = "time",
- Carousel = "carousel",
- Carousel3D = "3D Carousel",
- Linear = "linear",
- //Staff = "staff",
- Map = "map",
- Grid = "grid",
- Pile = "pileup",
- StackedTimeline = "stacked timeline",
- NoteTaking = "notetaking"
-}
-export interface CollectionViewProps extends FieldViewProps {
- isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
+interface CollectionViewProps_ extends FieldViewProps {
+ isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
layoutEngine?: () => string;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
// property overrides for child documents
- children?: never | (() => JSX.Element[]) | React.ReactNode;
childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
- childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+ childDocumentsActive?: () => boolean; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
childFitWidth?: (child: Doc) => boolean;
childShowTitle?: () => string;
childOpacity?: () => number;
- childContextMenuItems?: () => { script: ScriptField, label: string }[];
+ childContextMenuItems?: () => { script: ScriptField; label: string }[];
childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
childHideDecorationTitle?: () => boolean;
childHideResizeHandles?: () => boolean;
- childLayoutTemplate?: () => (Doc | undefined);// specify a layout Doc template to use for children of the collection
+ childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
childLayoutString?: string;
- childFreezeDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
childIgnoreNativeSize?: boolean;
childClickScript?: ScriptField;
childDoubleClickScript?: ScriptField;
+ //TODO: [AL] add these fields
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
}
+export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {}
@observer
export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() {
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(CollectionView, fieldStr);
+ }
@observable private static _safeMode = false;
- public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
+ public static SetSafeMode(safeMode: boolean) {
+ this._safeMode = safeMode;
+ }
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
constructor(props: any) {
super(props);
- runInAction(() => this._annotationKeySuffix = returnEmptyString);
+ runInAction(() => (this._annotationKeySuffix = returnEmptyString));
}
get collectionViewType(): CollectionViewType | undefined {
@@ -102,15 +87,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
if (CollectionView._safeMode) {
switch (viewField) {
case CollectionViewType.Freeform:
- case CollectionViewType.Schema: return CollectionViewType.Tree;
- case CollectionViewType.Invalid: return CollectionViewType.Freeform;
+ case CollectionViewType.Schema:
+ return CollectionViewType.Tree;
+ case CollectionViewType.Invalid:
+ return CollectionViewType.Freeform;
}
}
return viewField as any as CollectionViewType;
}
showIsTagged = () => {
- return (null);
+ return null;
// this section would display an icon in the bototm right of a collection to indicate that all
// photos had been processed through Google's content analysis API and Google's tags had been
// assigned to the documents googlePhotosTags field.
@@ -119,9 +106,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
// const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
// return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
//this.isContentActive();
- }
+ };
- screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
+ screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()));
+ // prettier-ignore
private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => {
TraceMobx();
if (type === undefined) return null;
@@ -144,110 +132,132 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />;
//case CollectionViewType.Staff: return <CollectionStaffView key="collview" {...props} />;
}
- }
+ };
setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
const subItems: ContextMenuProps[] = [];
- subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
+ subItems.push({ description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' });
if (addExtras && CollectionView._safeMode) {
- ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
+ ContextMenu.Instance.addItem({ description: 'Test Freeform', event: () => func(CollectionViewType.Invalid), icon: 'project-diagram' });
}
- subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
- subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
- subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" });
- subItems.push({ description: "Note taking", event: () => func(CollectionViewType.NoteTaking)._autoHeight = true, icon: "ellipsis-v" });
- subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" });
- subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" });
- subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" });
- subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" });
- subItems.push({ description: "3D Carousel", event: () => func(CollectionViewType.Carousel3D), icon: "columns" });
- !Doc.UserDoc().noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
- !Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
- subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
+ subItems.push({ description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' });
+ subItems.push({ description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' });
+ subItems.push({ description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._autoHeight = true), icon: 'ellipsis-v' });
+ subItems.push({ description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._autoHeight = true), icon: 'ellipsis-v' });
+ subItems.push({ description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' });
+ subItems.push({ description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' });
+ subItems.push({ description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' });
+ subItems.push({ description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' });
+ subItems.push({ description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' });
+ !Doc.noviceMode && subItems.push({ description: 'Pivot/Time', event: () => func(CollectionViewType.Time), icon: 'columns' });
+ !Doc.noviceMode && subItems.push({ description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' });
+ subItems.push({ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' });
if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
const existingVm = ContextMenu.Instance.findByDescription(category);
- const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
- catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" });
- !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: "eye" });
+ const catItems = existingVm && 'subitems' in existingVm ? existingVm.subitems : [];
+ catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' });
+ !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: 'eye' });
}
}
onContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- this.setupViewTypes("UI Controls...", vtype => {
- const newRendition = Doc.MakeAlias(this.rootDoc);
- newRendition._viewType = vtype;
- this.props.addDocTab(newRendition, "add:right");
- return newRendition;
- }, false);
+ if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== Doc.MainDocId) {
+ // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ this.setupViewTypes(
+ 'UI Controls...',
+ vtype => {
+ const newRendition = Doc.MakeAlias(this.rootDoc);
+ newRendition._viewType = vtype;
+ this.props.addDocTab(newRendition, 'add:right');
+ return newRendition;
+ },
+ false
+ );
- const options = cm.findByDescription("Options...");
- const optionItems = options && "subitems" in options ? options.subitems : [];
- !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null;
+ const options = cm.findByDescription('Options...');
+ const optionItems = options && 'subitems' in options ? options.subitems : [];
+ !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null;
if (this.rootDoc.childLayout instanceof Doc) {
- optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" });
+ optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, 'add:right'), icon: 'project-diagram' });
}
if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) {
- optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" });
+ optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, 'add:right'), icon: 'project-diagram' });
}
- !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" });
+ !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' });
- if (!Doc.UserDoc().noviceMode) {
+ if (!Doc.noviceMode) {
optionItems.push({
- description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram"
+ description: 'Create Branch',
+ event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'),
+ icon: 'project-diagram',
});
optionItems.push({
- description: "Pull Master", event: () => BranchTask(this.rootDoc, "pull"), icon: "project-diagram"
+ description: 'Pull Master',
+ event: () => BranchTask(this.rootDoc, 'pull'),
+ icon: 'project-diagram',
});
optionItems.push({
- description: "Merge Branches", event: () => BranchTask(this.rootDoc, "merge"), icon: "project-diagram"
+ description: 'Merge Branches',
+ event: () => BranchTask(this.rootDoc, 'merge'),
+ icon: 'project-diagram',
});
}
if (this.Document._viewType === CollectionViewType.Docking) {
- optionItems.push({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(Doc.UserDoc()), icon: "project-diagram" });
+ optionItems.push({ description: 'Create Dashboard', event: () => DashboardView.createNewDashboard(), icon: 'project-diagram' });
}
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
- if (!Doc.UserDoc().noviceMode && !this.rootDoc.annotationOn) {
- const existingOnClick = cm.findByDescription("OnClick...");
- const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
- const funcs = [{ key: "onChildClick", name: "On Child Clicked" }, { key: "onChildDoubleClick", name: "On Child Double Clicked" }];
- funcs.map(func => onClicks.push({
- description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
- const alias = Doc.MakeAlias(this.rootDoc);
- DocUtils.makeCustomViewClicked(alias, undefined, func.key);
- this.props.addDocTab(alias, "add:right");
- }
- }));
- DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
+ if (!Doc.noviceMode && !this.rootDoc.annotationOn) {
+ const existingOnClick = cm.findByDescription('OnClick...');
+ const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
+ const funcs = [
+ { key: 'onChildClick', name: 'On Child Clicked' },
+ { key: 'onChildDoubleClick', name: 'On Child Double Clicked' },
+ ];
+ funcs.map(func =>
+ onClicks.push({
+ description: `Edit ${func.name} script`,
+ icon: 'edit',
+ event: (obj: any) => {
+ const alias = Doc.MakeAlias(this.rootDoc);
+ DocUtils.makeCustomViewClicked(alias, undefined, func.key);
+ this.props.addDocTab(alias, 'add:right');
+ },
+ })
+ );
+ DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick =>
onClicks.push({
description: `Set child ${childClick.title}`,
- icon: "edit",
- event: () => Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
- }));
- !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
+ icon: 'edit',
+ event: () => (Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))),
+ })
+ );
+ !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
}
- if (!Doc.UserDoc().noviceMode) {
- const more = cm.findByDescription("More...");
- const moreItems = more && "subitems" in more ? more.subitems : [];
- moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) });
- !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+ if (!Doc.noviceMode) {
+ const more = cm.findByDescription('More...');
+ const moreItems = more && 'subitems' in more ? more.subitems : [];
+ moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) });
+ !more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' });
}
}
- }
+ };
bodyPanelWidth = () => this.props.PanelWidth();
+ childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles);
+ childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
- @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); }
-
- isContentActive = (outsideReaction?: boolean) => {
- return this.props.isContentActive();
+ @computed get childLayoutString() {
+ return StrCast(this.rootDoc.childLayoutString);
}
+
+ isContentActive = (outsideReaction?: boolean) => this.props.isContentActive();
+
render() {
TraceMobx();
const props: SubCollectionViewProps = {
@@ -263,12 +273,15 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
ScreenToLocalTransform: this.screenToLocalTransform,
childLayoutTemplate: this.childLayoutTemplate,
childLayoutString: this.childLayoutString,
+ childHideResizeHandles: this.childHideResizeHandles,
+ childHideDecorationTitle: this.childHideDecorationTitle,
CollectionView: this,
};
- return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
- style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}>
- {this.showIsTagged()}
- {this.renderSubView(this.collectionViewType, props)}
- </div>);
+ return (
+ <div className={'collectionView'} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}>
+ {this.showIsTagged()}
+ {this.renderSubView(this.collectionViewType, props)}
+ </div>
+ );
}
}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 7e57d0e89..2d08b1c09 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -1,39 +1,41 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import 'golden-layout/src/css/goldenlayout-base.css';
-import 'golden-layout/src/css/goldenlayout-dark-theme.css';
import { clamp } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
-import { FieldId } from "../../../fields/RefField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
-import { DocServer } from "../../DocServer";
+import { List } from '../../../fields/List';
+import { FieldId } from '../../../fields/RefField';
+import { listSpec } from '../../../fields/Schema';
+import { ScriptField } from '../../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
+import { DocServer } from '../../DocServer';
import { DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from "../../util/DragManager";
+import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { DashboardView } from '../DashboardView';
import { Colors, Shadows } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
+import { MainView } from '../MainView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DashFieldView } from '../nodes/formattedText/DashFieldView';
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
-import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
+import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionView, CollectionViewType } from './CollectionView';
-import "./TabDocView.scss";
-import React = require("react");
-const _global = (window /* browser */ || global /* node */) as any;
+import { CollectionView } from './CollectionView';
+import './TabDocView.scss';
+import React = require('react');
+const _global = (window /* browser */ || global) /* node */ as any;
interface TabDocViewProps {
documentId: FieldId;
@@ -50,9 +52,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
@observable _document: Doc | undefined;
@observable _view: DocumentView | undefined;
- @computed get layoutDoc() { return this._document && Doc.Layout(this._document); }
- @computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); }
- @computed get tabTextColor() { return this._document?.type === DocumentType.PRES ? "black" : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); }
+ @computed get layoutDoc() {
+ return this._document && Doc.Layout(this._document);
+ }
+ @computed get tabColor() {
+ return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor)));
+ }
+ @computed get tabTextColor() {
+ return this._document?.type === DocumentType.PRES ? 'black' : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color)));
+ }
// @computed get renderBounds() {
// const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
// const xbounds = bounds[2] - bounds[0];
@@ -61,78 +69,91 @@ export class TabDocView extends React.Component<TabDocViewProps> {
// return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
// }
- get stack() { return (this.props as any).glContainer.parent.parent; }
- get tab() { return (this.props as any).glContainer.tab; }
- get view() { return this._view; }
+ get stack() {
+ return (this.props as any).glContainer.parent.parent;
+ }
+ get tab() {
+ return (this.props as any).glContainer.tab;
+ }
+ get view() {
+ return this._view;
+ }
+ _lastTab: any;
+ _lastView: DocumentView | undefined;
@action
init = (tab: any, doc: Opt<Doc>) => {
if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true;
- if (tab.DashDoc !== doc && doc && tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+ if (tab.DashDoc !== doc && doc && tab.hasOwnProperty('contentItem') && tab.contentItem.config.type !== 'stack') {
tab._disposers = {} as { [name: string]: IReactionDisposer };
tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
tab.DashDoc = doc;
- CollectionDockingView.Instance.tabMap.add(tab);
const iconType: IconProp = Doc.toIcon(doc);
// setup the title element and set its size according to the # of chars in the title. Show the full title when clicked.
const titleEle = tab.titleElement[0];
- const iconWrap = document.createElement("div");
- const closeWrap = document.createElement("div");
-
+ const iconWrap = document.createElement('div');
+ const closeWrap = document.createElement('div');
titleEle.size = StrCast(doc.title).length + 3;
titleEle.value = doc.title;
- titleEle.onchange = undoBatch(action((e: any) => {
- titleEle.size = e.currentTarget.value.length + 3;
- Doc.GetProto(doc).title = e.currentTarget.value;
- }));
-
- const dragBtnDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction);
+ titleEle.onkeydown = (e: KeyboardEvent) => {
+ e.stopPropagation();
};
-
+ titleEle.onchange = undoBatch(
+ action((e: any) => {
+ titleEle.size = e.currentTarget.value.length + 3;
+ Doc.GetProto(doc).title = e.currentTarget.value;
+ })
+ );
if (tab.element[0].children[1].children.length === 1) {
- const toggle = document.createElement("div");
- toggle.style.width = "10px";
- toggle.style.height = "calc(100% - 2px)";
- toggle.style.left = "-2px";
- toggle.style.bottom = "1px";
- toggle.style.borderTopRightRadius = "7px";
- toggle.style.position = "relative";
- toggle.style.display = "inline-block";
- toggle.style.background = "transparent";
- toggle.onclick = (e: MouseEvent) => {
- if (tab.contentItem === tab.header.parent.getActiveContentItem()) {
- tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : StyleLayers.Background;
- }
- };
- iconWrap.className = "lm_iconWrap";
- iconWrap.id = "lm_iconWrap";
- closeWrap.className = "lm_iconWrap";
- closeWrap.id = "lm_closeWrap";
- closeWrap.onclick = (e: MouseEvent) => {
- tab.header.parent.contentItem.remove();
- Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true);
+ iconWrap.className = 'lm_iconWrap lm_moreInfo';
+ const dragBtnDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY),
+ returnFalse,
+ action(e => {
+ if (this.view) {
+ SelectionManager.SelectView(this.view, false);
+ let child = this.view.ContentDiv!.children[0];
+ while (child.children.length) {
+ const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
+ if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
+ if (next?.className?.toString().includes(DashFieldView.name)) break;
+ if (next) child = next;
+ else break;
+ }
+ simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+ } else {
+ this._activated = true;
+ setTimeout(() => this.view && SelectionManager.SelectView(this.view, false));
+ }
+ })
+ );
};
+
const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
- const closeIcon = <FontAwesomeIcon icon={"times"} />;
+ const closeIcon = <FontAwesomeIcon icon={'eye'} />;
ReactDOM.render(docIcon, iconWrap);
ReactDOM.render(closeIcon, closeWrap);
- // tab.element[0].append(closeWrap);
+ tab.reactComponents = [iconWrap, closeWrap];
tab.element[0].prepend(iconWrap);
- tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
+ tab._disposers.layerDisposer = reaction(
+ () => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
({ layer, color }) => {
// console.log("TabDocView: " + this.tabColor);
// console.log("lightOrDark: " + lightOrDark(this.tabColor));
const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color
titleEle.style.color = textColor;
- titleEle.style.backgroundColor = "transparent";
+ titleEle.style.backgroundColor = 'transparent';
iconWrap.style.color = textColor;
closeWrap.style.color = textColor;
- moreInfoDrag.style.backgroundColor = textColor;
- tab.element[0].style.background = !layer ? color : "dimgrey";
- }, { fireImmediately: true });
+ tab.element[0].style.background = !layer ? color : 'dimgrey';
+ },
+ { fireImmediately: true }
+ );
}
// shifts the focus to this tab when another tab is dragged over it
tab.element[0].onmouseenter = (e: MouseEvent) => {
@@ -142,142 +163,188 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
};
-
// select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
titleEle.onpointerdown = action((e: any) => {
- if (e.target.className !== "lm_iconWrap") {
+ if (e.target.className !== 'lm_iconWrap') {
if (this.view) SelectionManager.SelectView(this.view, false);
else this._activated = true;
if (Date.now() - titleEle.lastClick < 1000) titleEle.select();
titleEle.lastClick = Date.now();
- (document.activeElement !== titleEle) && titleEle.focus();
+ document.activeElement !== titleEle && titleEle.focus();
}
});
- tab._disposers.selectionDisposer = reaction(() => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc),
- action((selected) => {
+ tab._disposers.selectionDisposer = reaction(
+ () => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc),
+ action(selected => {
if (selected) this._activated = true;
const toggle = tab.element[0].children[1].children[0] as HTMLInputElement;
- selected && tab.contentItem !== tab.header.parent.getActiveContentItem() &&
- UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), "tab switch");
+ selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch');
// toggle.style.fontWeight = selected ? "bold" : "";
// toggle.style.textTransform = selected ? "uppercase" : "";
- }));
-
- //attach the selection doc buttons menu to the drag handle
- const stack: HTMLDivElement = tab.contentItem.parent;
- const header: HTMLDivElement = tab;
- stack.onscroll = action((e: any) => {
- console.log('scrolling...');
- });
- const moreInfoDrag = document.createElement("div");
- moreInfoDrag.className = "lm_iconWrap";
- tab._disposers.buttonDisposer = reaction(() => this.view, view =>
- view && [ReactDOM.render(<span><CollectionDockingViewMenu views={() => [view]} Stack={stack} /></span>, moreInfoDrag), tab._disposers.buttonDisposer?.()],
- { fireImmediately: true });
- // tab.reactComponents = [moreInfoDrag];
- // tab.element[0].children[3].before(moreInfoDrag);
+ })
+ );
// highlight the tab when the tab document is brushed in any part of the UI
- tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
- titleEle.value = title;
- // titleEle.style.padding = degree ? 0 : 2;
- // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
- }, { fireImmediately: true });
+ tab._disposers.reactionDisposer = reaction(
+ () => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }),
+ ({ title, degree }) => {
+ //titleEle.value = title;
+ // titleEle.style.padding = degree ? 0 : 2;
+ // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
+ },
+ { fireImmediately: true }
+ );
// clean up the tab when it is closed
- tab.closeElement.off('click') //unbind the current click handler
+ tab.closeElement
+ .off('click') //unbind the current click handler
.click(function () {
Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
- Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
SelectionManager.DeselectAll();
- tab.contentItem.remove();
+ UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab');
});
}
- }
+ };
/**
* Adds a document to the presentation view
**/
@action
- public static async PinDoc(doc: Doc, pinProps?: PinProps) {
- if (pinProps?.unpin) console.log('TODO: Remove UNPIN from this location');
- //add this new doc to props.Document
- const curPres = CurrentUserUtils.ActivePresentation;
- if (curPres) {
- if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; }
- const batch = UndoManager.StartBatch("pinning doc");
- const pinDoc = Doc.MakeAlias(doc);
- pinDoc.presentationTargetDoc = doc;
- pinDoc.title = doc.title + " - Slide";
- pinDoc.presMovement = PresMovement.Zoom;
- pinDoc.groupWithUp = false;
- pinDoc.context = curPres;
- const presArray: Doc[] = PresBox.Instance?.sortArray();
- const size: number = PresBox.Instance?._selectedArray.size;
- const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
- const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
- Doc.AddDocToList(curPres, "data", pinDoc, presSelected);
- if (!pinProps?.audioRange && duration !== undefined) {
- pinDoc.mediaStart = "manual";
- pinDoc.mediaStop = "manual";
- pinDoc.presStartTime = 0;
- pinDoc.presEndTime = duration;
- }
- //save position
- if (pinProps?.setPosition || pinDoc.isInkMask) {
- pinDoc.setPosition = true;
- pinDoc.y = doc.y;
- pinDoc.x = doc.x;
- pinDoc.presHideAfter = true;
- pinDoc.presHideBefore = true;
- pinDoc.title = doc.title + " (move)";
- pinDoc.presMovement = PresMovement.None;
- }
- if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
- const dview = CollectionDockingView.Instance.props.Document;
- const fieldKey = CollectionDockingView.Instance.props.fieldKey;
- const sublists = DocListCast(dview[fieldKey]);
- const tabs = Cast(sublists[0], Doc, null);
- const tabdocs = await DocListCastAsync(tabs?.data);
- runInAction(() => {
- if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) {
- tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs
- CollectionDockingView.AddSplit(curPres, "right");
+ public static PinDoc(docs: Doc | Doc[], pinProps?: PinProps) {
+ const docList = docs instanceof Doc ? [docs] : docs;
+ const batch = UndoManager.StartBatch('pinning doc');
+
+ // all docs will be added to the ActivePresentation as stored on CurrentUserUtils
+ const curPres = Doc.ActivePresentation;
+ curPres &&
+ docList.forEach(doc => {
+ // Edge Case 1: Cannot pin document to itself
+ if (doc === curPres) {
+ alert('Cannot pin presentation document to itself');
+ return;
+ }
+ const pinDoc = Doc.MakeAlias(doc);
+ pinDoc.presentationTargetDoc = doc;
+ pinDoc.title = doc.title + ' - Slide';
+ pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
+ pinDoc.presMovement = PresMovement.Zoom;
+ pinDoc.groupWithUp = false;
+ pinDoc.context = curPres;
+ // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time
+ pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
+ pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
+ pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
+ pinDoc.treeViewFieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
+ pinDoc.treeViewExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
+ pinDoc.treeViewGrowsHorizontally = true; // the document expands horizontally when displayed as a tree view header
+ pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
+ const presArray: Doc[] = PresBox.Instance?.sortArray();
+ const size: number = PresBox.Instance?._selectedArray.size;
+ const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
+ const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
+ // If pinWithView option set then update scale and x / y props of slide
+ if (pinProps?.pinWithView) {
+ const viewProps = pinProps.pinWithView;
+ pinDoc.presPinView = true;
+ pinDoc.presPinViewX = viewProps.bounds.left + viewProps.bounds.width / 2;
+ pinDoc.presPinViewY = viewProps.bounds.top + viewProps.bounds.height / 2;
+ pinDoc.presPinViewScale = viewProps.scale;
+ pinDoc.contentBounds = new List<number>([viewProps.bounds.left, viewProps.bounds.top, viewProps.bounds.left + viewProps.bounds.width, viewProps.bounds.top + viewProps.bounds.height]);
}
+ if (pinProps?.pinDocView) {
+ const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(pinDoc.type as any) || pinDoc._viewType === CollectionViewType.Stacking;
+ const pannable: boolean = (pinDoc.type === DocumentType.COL && doc._viewType === CollectionViewType.Freeform) || doc.type === DocumentType.IMG;
+ if (scrollable) {
+ const scroll = doc._scrollTop;
+ pinDoc.presPinView = true;
+ pinDoc.presPinViewScroll = scroll;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(doc.type as any)) {
+ pinDoc.presPinView = true;
+ pinDoc.presStartTime = doc._currentTimecode;
+ pinDoc.presEndTime = NumCast(doc._currentTimecode) + 0.1;
+ } else if (pannable) {
+ pinDoc.presPinView = true;
+ pinDoc.presPinViewX = pinDoc._panX;
+ pinDoc.presPinViewY = pinDoc._panY;
+ pinDoc.presPinViewScale = pinDoc._viewScale;
+ const pw = NumCast(pinProps.panelWidth);
+ const ph = NumCast(pinProps.panelHeight);
+ const ps = NumCast(pinDoc._viewScale);
+ if (pw && ph && ps) {
+ pinDoc.contentBounds = new List<number>([NumCast(pinDoc.panX) - pw / 2 / ps, NumCast(pinDoc.panY) - ph / 2 / ps, NumCast(pinDoc.panX) + pw / 2 / ps, NumCast(pinDoc.panY) + ph / 2 / ps]);
+ }
+ } else if (doc.type === DocumentType.COMPARISON) {
+ const width = doc._clipWidth;
+ pinDoc.presPinClipWidth = width;
+ pinDoc.presPinView = true;
+ }
+ }
+ pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
+ Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
+ if (!pinProps?.audioRange && duration !== undefined) {
+ pinDoc.mediaStart = 'manual';
+ pinDoc.mediaStop = 'manual';
+ pinDoc.presStartTime = NumCast(doc.clipStart);
+ pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
+ }
+ //save position
+ if (pinProps?.setPosition || pinDoc.isInkMask) {
+ pinDoc.setPosition = true;
+ pinDoc.y = doc.y;
+ pinDoc.x = doc.x;
+ pinDoc.presHideAfter = true;
+ pinDoc.presHideBefore = true;
+ pinDoc.title = doc.title + ' (move)';
+ pinDoc.presMovement = PresMovement.None;
+ }
+ if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
PresBox.Instance?._selectedArray.clear();
pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
- DocumentManager.Instance.jumpToDocument(doc, false, undefined);
- batch.end();
});
+ if (
+ CollectionDockingView.Instance &&
+ !Array.from(CollectionDockingView.Instance.tabMap)
+ .map(d => d.DashDoc)
+ .includes(curPres)
+ ) {
+ const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []);
+ if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
+ CollectionDockingView.AddSplit(curPres, 'right');
+ setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things
}
+ setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
}
componentDidMount() {
- new _global.ResizeObserver(action((entries: any) => {
- for (const entry of entries) {
- this._panelWidth = entry.contentRect.width;
- this._panelHeight = entry.contentRect.height;
- }
- })).observe(this.props.glContainer._element[0]);
- this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
+ new _global.ResizeObserver(
+ action((entries: any) => {
+ for (const entry of entries) {
+ this._panelWidth = entry.contentRect.width;
+ this._panelHeight = entry.contentRect.height;
+ }
+ })
+ ).observe(this.props.glContainer._element[0]);
+ this.props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
// this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
// ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
// { fireImmediately: true });
}
+ componentDidUpdate() {
+ this._view && DocumentManager.Instance.AddView(this._view);
+ }
componentWillUnmount() {
this._tabReaction?.();
- this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab);
+ this._view && DocumentManager.Instance.RemoveView(this._view);
- this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
+ this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
}
@action.bound
private onActiveContentItemChanged(contentItem: any) {
if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
- (CollectionDockingView.Instance as any)._goldenLayout?.isInitialised && CollectionDockingView.Instance.stateChanged();
!this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
}
}
@@ -291,23 +358,31 @@ export class TabDocView extends React.Component<TabDocViewProps> {
// inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
addDocTab = (doc: Doc, location: string) => {
SelectionManager.DeselectAll();
- const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":");
- const locationParams = locationFields.length > 1 ? locationFields[1] : "";
+ const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':');
+ const locationParams = locationFields.length > 1 ? locationFields[1] : '';
switch (locationFields[0]) {
- case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
- case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
- case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
- case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
- case "lightbox": {
- // TabDocView.PinDoc(doc, { hidePresBox: true });
- return LightboxView.AddDocTab(doc, location);
- }
- case "inPlace":
- case "add":
+ case 'dashboard':
+ return DashboardView.openDashboard(doc);
+ case 'close':
+ return CollectionDockingView.CloseSplit(doc, locationParams);
+ case 'fullScreen':
+ return CollectionDockingView.OpenFullScreen(doc);
+ case 'replace':
+ return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
+ // case "lightbox": {
+ // // TabDocView.PinDoc(doc, { hidePresBox: true });
+ // return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab);
+ // }
+ case 'lightbox':
+ return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab);
+ case 'toggle':
+ return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack);
+ case 'inPlace':
+ case 'add':
default:
return CollectionDockingView.AddSplit(doc, locationParams, this.stack);
}
- }
+ };
remDocTab = (doc: Doc | Doc[]) => {
if (doc === this._document) {
SelectionManager.DeselectAll();
@@ -315,11 +390,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
return true;
}
return false;
- }
+ };
getCurrentFrame = () => {
return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame);
- }
+ };
@action
focusFunc = (doc: Doc, options?: DocFocusOptions) => {
const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap;
@@ -327,94 +402,113 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const focusSpeed = 1000;
shrinkwrap();
this._document._viewTransition = `transform ${focusSpeed}ms`;
- setTimeout(action(() => {
- this._document!._viewTransition = undefined;
- options?.afterFocus?.(false);
- }), focusSpeed);
+ setTimeout(
+ action(() => {
+ this._document!._viewTransition = undefined;
+ options?.afterFocus?.(false);
+ }),
+ focusSpeed
+ );
} else {
options?.afterFocus?.(false);
}
if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
}
- }
+ };
active = () => this._isActive;
+ @observable _forceInvalidateScreenToLocal = 0;
ScreenToLocalTransform = () => {
+ this._forceInvalidateScreenToLocal;
const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
- return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY);
- }
+ return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
+ };
PanelWidth = () => this._panelWidth;
PanelHeight = () => this._panelHeight;
miniMapColor = () => this.tabColor;
tabView = () => this._view;
- disableMinimap = () => !this._document || (this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform);
+ disableMinimap = () => !this._document || this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform;
hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap);
- @computed get layerProvider() { return this._document && DefaultLayerProvider(this._document); }
@computed get docView() {
- return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) :
- <><DocumentView key={this._document[Id]} ref={action((r: DocumentView) => this._view = r)}
- renderDepth={0}
- Document={this._document}
- DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- isContentActive={returnTrue}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- layerProvider={this.layerProvider}
- styleProvider={DefaultStyleProvider}
- docFilters={CollectionDockingView.Instance.childDocFilters}
- docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
- searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
- addDocument={undefined}
- removeDocument={this.remDocTab}
- addDocTab={this.addDocTab}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- dontCenter={"y"}
- rootSelected={returnTrue}
- whenChildContentsActiveChanged={emptyFunction}
- focus={this.focusFunc}
- docViewPath={returnEmptyDoclist}
- bringToFront={emptyFunction}
- pinToPres={TabDocView.PinDoc} />
- <TabMinimapView key="minimap"
- hideMinimap={this.hideMinimap}
- addDocTab={this.addDocTab}
- PanelHeight={this.PanelHeight}
+ return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? null : (
+ <>
+ <DocumentView
+ key={this._document[Id]}
+ ref={action((r: DocumentView) => {
+ this._lastView && DocumentManager.Instance.RemoveView(this._lastView);
+ this._view = r;
+ this._lastView = this._view;
+ })}
+ renderDepth={0}
+ Document={this._document}
+ DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ onBrowseClick={MainView.Instance.exploreMode}
+ isContentActive={returnTrue}
PanelWidth={this.PanelWidth}
- background={this.miniMapColor}
- document={this._document}
- tabView={this.tabView} />
- <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.hideMinimap ? "Open minimap" : "Close minimap"}</div>}>
- <div className="miniMap-hidden"
+ PanelHeight={this.PanelHeight}
+ styleProvider={DefaultStyleProvider}
+ docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+ addDocument={undefined}
+ removeDocument={this.remDocTab}
+ addDocTab={this.addDocTab}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ dontCenter={'y'}
+ rootSelected={returnTrue}
+ whenChildContentsActiveChanged={emptyFunction}
+ focus={this.focusFunc}
+ docViewPath={returnEmptyDoclist}
+ bringToFront={emptyFunction}
+ pinToPres={TabDocView.PinDoc}
+ />
+ <TabMinimapView key="minimap" hideMinimap={this.hideMinimap} addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
+ <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.hideMinimap ? 'Open minimap' : 'Close minimap'}</div>}>
+ <div
+ className="miniMap-hidden"
style={{
- display: this.disableMinimap() || this._document._viewType !== "freeform" ? "none" : undefined,
+ display: this.disableMinimap() || this._document._viewType !== 'freeform' ? 'none' : undefined,
color: this._document.hideMinimap ? Colors.BLACK : Colors.WHITE,
backgroundColor: this._document.hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE,
- boxShadow: this._document.hideMinimap ? Shadows.STANDARD_SHADOW : undefined
+ boxShadow: this._document.hideMinimap ? Shadows.STANDARD_SHADOW : undefined,
}}
onPointerDown={e => e.stopPropagation()}
- onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
- <FontAwesomeIcon icon={"globe-asia"} size="lg" />
+ onClick={action(e => {
+ e.stopPropagation();
+ this._document!.hideMinimap = !this._document!.hideMinimap;
+ })}>
+ <FontAwesomeIcon icon={'globe-asia'} size="lg" />
</div>
</Tooltip>
- </>;
+ </>
+ );
}
render() {
return (
- <div className="collectionDockingView-content" style={{
- fontFamily: Doc.UserDoc().renderStyle === "comic" ? "Comic Sans MS" : undefined,
- height: "100%", width: "100%"
- }} ref={ref => {
- if (this._mainCont = ref) {
- (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
- DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
- }
- }} >
+ <div
+ className="collectionDockingView-content"
+ style={{
+ fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
+ height: '100%',
+ width: '100%',
+ }}
+ ref={ref => {
+ if ((this._mainCont = ref)) {
+ if (this._lastTab) {
+ this._view && DocumentManager.Instance.RemoveView(this._view);
+ }
+ this._lastTab = this.tab;
+ (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
+ DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
+ new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
+ }
+ }}>
{this.docView}
- </div >
+ </div>
);
}
}
@@ -432,18 +526,38 @@ interface TabMinimapViewProps {
export class TabMinimapView extends React.Component<TabMinimapViewProps> {
static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
if (doc) {
- switch (property.split(":")[0]) {
- default: return DefaultStyleProvider(doc, props, property);
- case StyleProp.PointerEvents: return "none";
+ switch (property.split(':')[0]) {
+ default:
+ return DefaultStyleProvider(doc, props, property);
+ case StyleProp.PointerEvents:
+ return 'none';
case StyleProp.DocContents:
- const background = doc.type === DocumentType.PDF ? "red" : doc.type === DocumentType.IMG ? "blue" : doc.type === DocumentType.RTF ? "orange" :
- doc.type === DocumentType.VID ? "purple" : doc.type === DocumentType.WEB ? "yellow" : doc.type === DocumentType.MAP ? "blue" : "gray";
- return doc.type === DocumentType.COL ?
- undefined :
- <div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: "absolute", display: "block", background }} />;
+ const background = ((type: DocumentType) => {
+ switch (type) {
+ case DocumentType.PDF:
+ return 'pink';
+ case DocumentType.AUDIO:
+ return 'lightgreen';
+ case DocumentType.WEB:
+ return 'brown';
+ case DocumentType.IMG:
+ return 'blue';
+ case DocumentType.MAP:
+ return 'orange';
+ case DocumentType.VID:
+ return 'purple';
+ case DocumentType.RTF:
+ return 'yellow';
+ case DocumentType.COL:
+ return undefined;
+ default:
+ return 'gray';
+ }
+ })(doc.type as DocumentType);
+ return !background ? undefined : <div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: 'absolute', display: 'block', background }} />;
}
}
- }
+ };
@computed get renderBounds() {
const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView;
const bounds = compView?.freeformData?.(true)?.bounds;
@@ -459,20 +573,27 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
const doc = this.props.document;
const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
const miniSize = this.returnMiniSize();
- doc && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
- doc._panX = clamp(NumCast(doc._panX) + delta[0] / miniSize * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
- doc._panY = clamp(NumCast(doc._panY) + delta[1] / miniSize * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
- return false;
- }), emptyFunction, emptyFunction);
- }
+ doc &&
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e: PointerEvent, down: number[], delta: number[]) => {
+ doc._panX = clamp(NumCast(doc._panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
+ doc._panY = clamp(NumCast(doc._panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
+ return false;
+ }),
+ emptyFunction,
+ emptyFunction
+ );
+ };
render() {
- if (!this.renderBounds) return (null);
- const miniWidth = this.props.PanelWidth() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100;
- const miniHeight = this.props.PanelHeight() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100;
- const miniLeft = 50 + (NumCast(this.props.document._panX) - this.renderBounds.cx) / this.renderBounds.dim * 100 - miniWidth / 2;
- const miniTop = 50 + (NumCast(this.props.document._panY) - this.renderBounds.cy) / this.renderBounds.dim * 100 - miniHeight / 2;
+ if (!this.renderBounds) return null;
+ const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim) * 100;
+ const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim) * 100;
+ const miniLeft = 50 + ((NumCast(this.props.document._panX) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
+ const miniTop = 50 + ((NumCast(this.props.document._panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2;
const miniSize = this.returnMiniSize();
- return this.props.hideMinimap() ? (null) :
+ return this.props.hideMinimap() ? null : (
<div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
<CollectionFreeFormView
Document={this.props.document}
@@ -502,17 +623,17 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
whenChildContentsActiveChanged={emptyFunction}
focus={DocUtils.DefaultFocus}
styleProvider={TabMinimapView.miniStyleProvider}
- layerProvider={undefined}
addDocTab={this.props.addDocTab}
pinToPres={TabDocView.PinDoc}
- docFilters={CollectionDockingView.Instance.childDocFilters}
- docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
- searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
- fitContentsToDoc={returnTrue}
+ docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+ fitContentsToBox={returnTrue}
/>
- <div className="miniOverlay" onPointerDown={this.miniDown} >
- <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% `, }} />
+ <div className="miniOverlay" onPointerDown={this.miniDown}>
+ <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% ` }} />
</div>
- </div>;
+ </div>
+ );
}
}
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 2e33d3564..f587dbbf6 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -21,7 +21,9 @@
}
.treeView-bulletIcons {
- width: $TREE_BULLET_WIDTH;
+ // width: $TREE_BULLET_WIDTH;
+ width: 100%;
+ height: 100%;
.treeView-expandIcon {
display: none;
@@ -42,24 +44,48 @@
}
}
+ .treeView-bulletIcons:hover img {
+ left: 14px;
+ position: absolute;
+ transform-origin: center left;
+ transform: scale(6);
+ pointer-events: none;
+ }
+
.bullet {
position: relative;
width: $TREE_BULLET_WIDTH;
color: $medium-gray;
margin-top: 3px;
- transform: scale(1.3, 1.3);
+ // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder.
border: #80808030 1px solid;
border-radius: 4px;
}
}
-.treeView-container-outline-active
+.treeView-sorting {
+ position: absolute;
+ height: max-content;
+ pointer-events: none;
+ color: white;
+ border-radius: 4px;
+ font-size: 10px;
+}
+
.treeView-container-active {
+ cursor: default;
+}
+
+.treeView-container-outline-active .treeView-container-active {
z-index: 100;
position: relative;
pointer-events: all;
}
+.bullet:hover {
+ z-index: 100;
+}
+
.treeView-openRight {
display: none;
height: 17px;
@@ -115,6 +141,7 @@
align-items: center;
margin-left: 0.25rem;
opacity: 0.75;
+ pointer-events: all;
cursor: pointer;
>svg {
@@ -123,7 +150,9 @@
}
>svg {
- display: none;
+ //display: none;
+ opacity: 0;
+ pointer-events: none;
}
}
}
@@ -147,9 +176,12 @@
}
.treeView-rightButtons {
+
>svg,
.styleProvider-treeView-icon {
display: inherit;
+ opacity: unset;
+ pointer-events: unset;
}
}
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index f30a6ac67..aa1330762 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,7 +1,8 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym, StrListCast } from '../../../fields/Doc';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
@@ -9,28 +10,28 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils, returnOne } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from "../../util/DragManager";
+import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { EditableView } from "../EditableView";
+import { EditableView } from '../EditableView';
import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
-import { DocumentView, DocumentViewProps, StyleProviderFunc, DocumentViewInternal } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { KeyValueBox } from '../nodes/KeyValueBox';
-import { SliderBox } from '../nodes/SliderBox';
-import { StyleProp, testDocProps } from '../StyleProvider';
-import { CollectionTreeView } from './CollectionTreeView';
-import { CollectionView, CollectionViewType } from './CollectionView';
-import "./TreeView.scss";
-import React = require("react");
+import { StyleProp } from '../StyleProvider';
+import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
+import { CollectionView } from './CollectionView';
+import './TreeView.scss';
+import React = require('react');
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -54,7 +55,7 @@ export interface TreeViewProps {
indentDocument?: (editTitle: boolean) => void;
outdentDocument?: (editTitle: boolean) => void;
ScreenToLocalTransform: () => Transform;
- contextMenuItems: { script: ScriptField, filter: ScriptField, icon: string, label: string }[];
+ contextMenuItems: { script: ScriptField; filter: ScriptField; icon: string; label: string }[];
dontRegisterView?: boolean;
styleProvider?: StyleProviderFunc | undefined;
treeViewHideHeaderFields: () => boolean;
@@ -63,20 +64,32 @@ export interface TreeViewProps {
onChildClick?: () => ScriptField;
skipFields?: string[];
firstLevel: boolean;
+ // TODO: [AL] add these
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ hierarchyIndex?: number[];
}
-const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("px", "")); };
+const treeBulletWidth = function () {
+ return Number(TREE_BULLET_WIDTH.replace('px', ''));
+};
-@observer
+export enum TreeSort {
+ Up = 'up',
+ Down = 'down',
+ Zindex = 'z',
+ None = 'none',
+}
/**
* Renders a treeView of a collection of documents
- *
+ *
* special fields:
* treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden
* treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
+@observer
export class TreeView extends React.Component<TreeViewProps> {
- static _editTitleOnLoad: Opt<{ id: string, parent: TreeView | CollectionTreeView | undefined }>;
+ static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>;
static _openTitleScript: Opt<ScriptField | undefined>;
static _openLevelScript: Opt<ScriptField | undefined>;
private _header: React.RefObject<HTMLDivElement> = React.createRef();
@@ -87,7 +100,9 @@ export class TreeView extends React.Component<TreeViewProps> {
private _openScript: (() => ScriptField) | undefined;
private _treedropDisposer?: DragManager.DragDropDisposer;
- get treeViewOpenIsTransient() { return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc); }
+ get treeViewOpenIsTransient() {
+ return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc);
+ }
set treeViewOpen(c: boolean) {
if (this.treeViewOpenIsTransient) this._transientOpenState = c;
else {
@@ -98,80 +113,138 @@ export class TreeView extends React.Component<TreeViewProps> {
@observable _transientOpenState = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
@observable _editTitle: boolean = false;
@observable _dref: DocumentView | undefined | null;
- get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive
+ get displayName() {
+ return 'TreeView(' + this.props.document.title + ')';
+ } // this makes mobx trace() statements more descriptive
get defaultExpandedView() {
- return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey :
- this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "layout") :
- this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields");
- }
-
- @computed get doc() { return this.props.document; }
- @computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._transientOpenState; }
- @computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; }
- @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); }
- @computed get dataDoc() { return this.doc[DataSym]; }
- @computed get layoutDoc() { return Doc.Layout(this.doc); }
- @computed get fieldKey() { return Doc.LayoutFieldKey(this.doc); }
- @computed get childDocs() { return this.childDocList(this.fieldKey); }
- @computed get childLinks() { return this.childDocList("links"); }
- @computed get childAliases() { return this.childDocList("aliases"); }
- @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); }
- @computed get selected() { return SelectionManager.IsSelected(this._docRef); }
+ return this.doc.viewType === CollectionViewType.Docking
+ ? this.fieldKey
+ : this.props.treeView.dashboardMode
+ ? this.fieldKey
+ : this.props.treeView.fileSysMode
+ ? this.doc.isFolder
+ ? this.fieldKey
+ : 'aliases' // for displaying
+ : this.props.treeView.outlineMode || this.childDocs
+ ? this.fieldKey
+ : Doc.noviceMode
+ ? 'layout'
+ : StrCast(this.props.treeView.doc.treeViewExpandedView, 'fields');
+ }
+
+ @computed get doc() {
+ return this.props.document;
+ }
+ @computed get treeViewOpen() {
+ return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeViewOpen', 'boolean', true)) || this._transientOpenState;
+ }
+ @computed get treeViewExpandedView() {
+ return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView;
+ }
+ @computed get MAX_EMBED_HEIGHT() {
+ return NumCast(this.props.containerCollection.maxEmbedHeight, 200);
+ }
+ @computed get dataDoc() {
+ return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym];
+ }
+ @computed get layoutDoc() {
+ return Doc.Layout(this.doc);
+ }
+ @computed get fieldKey() {
+ return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc));
+ }
+ @computed get childDocs() {
+ return this.childDocList(this.fieldKey);
+ }
+ @computed get childLinks() {
+ return this.childDocList('links');
+ }
+ @computed get childAliases() {
+ return this.childDocList('aliases');
+ }
+ @computed get childAnnos() {
+ return this.childDocList(this.fieldKey + '-annotations');
+ }
+ @computed get selected() {
+ return SelectionManager.IsSelected(this._docRef);
+ }
// SelectionManager.Views().lastElement()?.props.Document === this.props.document; }
+ @observable _presTimer!: NodeJS.Timeout;
+ @observable _presKeyEventsActive: boolean = false;
+
+ @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>();
+ // the selected item's index
+ @computed get itemIndex() {
+ return NumCast(this.doc._itemIndex);
+ }
+ // the item that's active
+ @computed get activeItem() {
+ return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined;
+ }
+ @computed get targetDoc() {
+ return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
+ }
+
childDocList(field: string) {
const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
- return (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
+ return (
+ (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
(layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
- DocListCastOrNull(this.doc[field]); // otherwise use the document's data field
+ DocListCastOrNull(this.doc[field])
+ ); // otherwise use the document's data field
}
@undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
- if (this.doc !== target && addDoc !== returnFalse) { // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
+ if (this.doc !== target && addDoc !== returnFalse) {
+ // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
if (this.props.removeDoc?.(doc) === true) {
return addDoc(doc);
}
}
return false;
- }
+ };
@undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
this.props.treeView.props.select(false);
const ind = this.dataDoc[key].indexOf(doc);
const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false);
return res;
- }
+ };
@action setEditTitle = (docView?: DocumentView) => {
this._selDisposer?.();
if (!docView) {
this._editTitle = false;
- }
- else if (docView.isSelected()) {
+ } else if (docView.isSelected()) {
const doc = docView.Document;
SelectionManager.SelectSchemaViewDoc(doc);
this._editTitle = true;
- this._selDisposer = reaction(() => SelectionManager.SelectedSchemaDoc(), seldoc => seldoc !== doc && this.setEditTitle(undefined));
+ this._selDisposer = reaction(
+ () => SelectionManager.SelectedSchemaDoc(),
+ seldoc => seldoc !== doc && this.setEditTitle(undefined)
+ );
} else {
docView.select(false);
}
- }
+ };
@action
openLevel = (docView: DocumentView) => {
if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) {
this.treeViewOpen = !this.treeViewOpen;
} else {
// choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias
- // this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right");
- const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
- this.props.addDocTab(bestAlias ?? Doc.MakeAlias(this.props.document), "add:right");
-
+ const bestAlias =
+ docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+ const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail);
+ this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), 'lightbox');
}
- }
+ };
+
constructor(props: any) {
super(props);
if (!TreeView._openLevelScript) {
- TreeView._openTitleScript = ScriptField.MakeScript("scriptContext.setEditTitle(documentView)", { scriptContext: "any", documentView: "any" });
- TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: "any", documentView: "any" });
+ TreeView._openTitleScript = ScriptField.MakeScript('scriptContext.setEditTitle(documentView)', { scriptContext: 'any', documentView: 'any' });
+ TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: 'any', documentView: 'any' });
}
this._openScript = Doc.IsSystem(this.props.document) ? undefined : () => TreeView._openLevelScript!;
this._editTitleScript = Doc.IsSystem(this.props.document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
@@ -180,63 +253,83 @@ export class TreeView extends React.Component<TreeViewProps> {
_treeEle: any;
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.doc);
+ ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.doc);
if (this._treeEle) this.props.unobserveHeight(this._treeEle);
- this.props.observeHeight(this._treeEle = ele);
- }
+ this.props.observeHeight((this._treeEle = ele));
+ };
componentWillUnmount() {
this._selDisposer?.();
this._treeEle && this.props.unobserveHeight(this._treeEle);
- document.removeEventListener("pointermove", this.onDragMove, true);
- document.removeEventListener("pointermove", this.onDragUp, true);
+ document.removeEventListener('pointermove', this.onDragMove, true);
+ document.removeEventListener('pointermove', this.onDragUp, true);
+ // TODO: [AL] add these
+ this.props.hierarchyIndex !== undefined && this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex);
}
- onDragUp = (e: PointerEvent) => {
- document.removeEventListener("pointerup", this.onDragUp, true);
- document.removeEventListener("pointermove", this.onDragMove, true);
+ componentDidUpdate() {
+ this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
}
+
+ componentDidMount() {
+ this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+ }
+
+ onDragUp = (e: PointerEvent) => {
+ document.removeEventListener('pointerup', this.onDragUp, true);
+ document.removeEventListener('pointermove', this.onDragMove, true);
+ };
onPointerEnter = (e: React.PointerEvent): void => {
this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header.current!.className = "treeView-header";
- document.removeEventListener("pointermove", this.onDragMove, true);
- document.removeEventListener("pointerup", this.onDragUp, true);
- document.addEventListener("pointermove", this.onDragMove, true);
- document.addEventListener("pointerup", this.onDragUp, true);
+ this._header.current!.className = 'treeView-header';
+ document.removeEventListener('pointermove', this.onDragMove, true);
+ document.removeEventListener('pointerup', this.onDragUp, true);
+ document.addEventListener('pointermove', this.onDragMove, true);
+ document.addEventListener('pointerup', this.onDragUp, true);
}
- }
+ };
onPointerLeave = (e: React.PointerEvent): void => {
Doc.UnBrushDoc(this.dataDoc);
- if (this._header.current?.className !== "treeView-header-editing") {
- this._header.current!.className = "treeView-header";
+ if (this._header.current?.className !== 'treeView-header-editing') {
+ this._header.current!.className = 'treeView-header';
}
- document.removeEventListener("pointerup", this.onDragUp, true);
- document.removeEventListener("pointermove", this.onDragMove, true);
- }
- // TODO: Parker look at this
+ document.removeEventListener('pointerup', this.onDragUp, true);
+ document.removeEventListener('pointermove', this.onDragMove, true);
+ };
onDragMove = (e: PointerEvent): void => {
Doc.UnBrushDoc(this.dataDoc);
const pt = [e.clientX, e.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length);
- this._header.current!.className = "treeView-header";
+ const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+ this._header.current!.className = 'treeView-header';
if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) {
- if (inside) this._header.current!.className += " treeView-header-inside";
- else if (before) this._header.current!.className += " treeView-header-above";
- else if (!before) this._header.current!.className += " treeView-header-below";
+ if (inside) this._header.current!.className += ' treeView-header-inside';
+ else if (before) this._header.current!.className += ' treeView-header-above';
+ else if (!before) this._header.current!.className += ' treeView-header-below';
}
e.stopPropagation();
- }
+ };
public static makeTextBullet() {
- const bullet = Docs.Create.TextDocument("-text-", {
- layout: CollectionView.LayoutString("data"),
- title: "-title-",
- treeViewExpandedViewLock: true, treeViewExpandedView: "data",
- _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline",
- x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, backgroundColor: "transparent", _width: 1000, _height: 10
+ const bullet = Docs.Create.TextDocument('-text-', {
+ layout: CollectionView.LayoutString('data'),
+ title: '-title-',
+ treeViewExpandedViewLock: true,
+ treeViewExpandedView: 'data',
+ _viewType: CollectionViewType.Tree,
+ hideLinkButton: true,
+ _showSidebar: true,
+ treeViewType: TreeViewType.outline,
+ x: 0,
+ y: 0,
+ _xMargin: 0,
+ _yMargin: 0,
+ _autoHeight: true,
+ _singleLine: true,
+ _width: 1000,
+ _height: 10,
});
Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
Doc.GetProto(bullet).data = new List<Doc>([]);
@@ -248,30 +341,31 @@ export class TreeView extends React.Component<TreeViewProps> {
const bullet = TreeView.makeTextBullet();
TreeView._editTitleOnLoad = { id: bullet[Id], parent: this };
return this.props.addDocument(bullet);
- }
+ };
makeFolder = () => {
- const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
+ const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true });
TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView };
return this.props.addDocument(folder);
- }
+ };
deleteItem = () => this.props.removeDoc?.(this.doc);
preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
- dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction);
- }
+ dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
+ };
@undoBatch
treeDrop = (e: Event, de: DragManager.DropEvent) => {
const pt = [de.x, de.y];
- const rect = this._header.current!.getBoundingClientRect();
+ if (!this._header.current) return;
+ const rect = this._header.current.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length);
+ const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
if (de.complete.linkDragData) {
const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
const destDoc = this.doc;
- DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", "");
+ DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, 'tree link', '');
e.stopPropagation();
}
const docDragData = de.complete.docDragData;
@@ -281,17 +375,16 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}
}
- }
+ };
dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
- const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add")) || forceAdd;
- const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false;
- const addDoc = !inside ? parentAddDoc :
- (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
- const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument;
+ const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd;
+ const localAdd = (doc: Doc) => (Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false);
+ const addDoc = !inside ? parentAddDoc : (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
+ const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
if (canAdd) {
- return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false));
+ return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
}
return false;
}
@@ -302,7 +395,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle());
const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
- }
+ };
docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
getTransform = () => this.refTransform(this._tref.current);
docWidth = () => {
@@ -310,23 +403,26 @@ export class TreeView extends React.Component<TreeViewProps> {
const aspect = Doc.NativeAspect(layoutDoc);
if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]());
if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth()));
- return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]());
- }
+ return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]());
+ };
docHeight = () => {
const layoutDoc = this.layoutDoc;
- return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
- const aspect = Doc.NativeAspect(layoutDoc);
- if (aspect) return this.docWidth() / (aspect || 1);
- return layoutDoc._fitWidth ?
- (!Doc.NativeHeight(this.doc) ?
- NumCast(this.props.containerCollection._height)
- :
- Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc)) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))
- ))
- :
- (layoutDoc[HeightSym]() || 50);
- })()));
- }
+ return Math.max(
+ 70,
+ Math.min(
+ this.MAX_EMBED_HEIGHT,
+ (() => {
+ const aspect = Doc.NativeAspect(layoutDoc);
+ if (aspect) return this.docWidth() / (aspect || 1);
+ return layoutDoc._fitWidth
+ ? !Doc.NativeHeight(this.doc)
+ ? NumCast(this.props.containerCollection._height)
+ : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
+ : layoutDoc[HeightSym]() || 50;
+ })()
+ )
+ );
+ };
@computed get expandedField() {
const ids: { [key: string]: string } = {};
@@ -335,11 +431,11 @@ export class TreeView extends React.Component<TreeViewProps> {
doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
for (const key of Object.keys(ids).slice().sort()) {
- if (this.props.skipFields?.includes(key) || key === "title" || key === "treeViewOpen") continue;
+ if (this.props.skipFields?.includes(key) || key === 'title' || key === 'treeViewOpen') continue;
const contents = doc[key];
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
- if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) {
+ if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
@@ -347,160 +443,273 @@ export class TreeView extends React.Component<TreeViewProps> {
return added;
};
const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
- contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents),
- this.props.treeView, this, doc, undefined, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive,
- this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields,
- [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems());
+ contentElement = TreeView.GetChildElements(
+ contents instanceof Doc ? [contents] : DocListCast(contents),
+ this.props.treeView,
+ this,
+ doc,
+ undefined,
+ this.props.containerCollection,
+ this.props.prevSibling,
+ addDoc,
+ remDoc,
+ this.move,
+ this.props.dropAction,
+ this.props.addDocTab,
+ this.titleStyleProvider,
+ this.props.ScreenToLocalTransform,
+ this.props.isContentActive,
+ this.props.panelWidth,
+ this.props.renderDepth,
+ this.props.treeViewHideHeaderFields,
+ [...this.props.renderedIds, doc[Id]],
+ this.props.onCheckedClick,
+ this.props.onChildClick,
+ this.props.skipFields,
+ false,
+ this.props.whenChildContentsActiveChanged,
+ this.props.dontRegisterView,
+ emptyFunction,
+ emptyFunction,
+ this.childContextMenuItems(),
+ // TODO: [AL] Add these
+ this.props.AddToMap,
+ this.props.RemFromMap,
+ this.props.hierarchyIndex
+ );
} else {
- contentElement = <EditableView key="editableView"
- contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
- height={13}
- fontSize={12}
- GetValue={() => Field.toKeyValueString(doc, key)}
- SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />;
+ contentElement = (
+ <EditableView
+ key="editableView"
+ contents={contents !== undefined ? Field.toString(contents as Field) : 'null'}
+ height={13}
+ fontSize={12}
+ GetValue={() => Field.toKeyValueString(doc, key)}
+ SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)}
+ />
+ );
}
- rows.push(<div style={{ display: "flex" }} key={key}>
- <span style={{ fontWeight: "bold" }}>{key + ":"}</span>
- &nbsp;
- {contentElement}
- </div>);
+ rows.push(
+ <div style={{ display: 'flex' }} key={key}>
+ <span style={{ fontWeight: 'bold' }}>{key + ':'}</span>
+ &nbsp;
+ {contentElement}
+ </div>
+ );
}
- rows.push(<div style={{ display: "flex" }} key={"newKeyValue"}>
- <EditableView
- key="editableView"
- contents={"+key:value"}
- height={13}
- fontSize={12}
- GetValue={returnEmptyString}
- SetValue={value => value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true)} />
- </div>);
+ rows.push(
+ <div style={{ display: 'flex' }} key={'newKeyValue'}>
+ <EditableView
+ key="editableView"
+ contents={'+key:value'}
+ height={13}
+ fontSize={12}
+ GetValue={returnEmptyString}
+ SetValue={value => value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)}
+ />
+ </div>
+ );
return rows;
}
- rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1);
- rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+ rtfWidth = () => {
+ const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+ return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1);
+ };
+ rtfHeight = () => {
+ const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+ return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+ };
rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth());
expandPanelHeight = () => {
if (this.layoutDoc._fitWidth) return this.docHeight();
const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym]();
const docAspect = this.docWidth() / this.docHeight();
- return (docAspect < aspect) ? this.docWidth() / aspect : this.docHeight();
- }
+ return docAspect < aspect ? this.docWidth() / aspect : this.docHeight();
+ };
expandPanelWidth = () => {
if (this.layoutDoc._fitWidth) return this.docWidth();
const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym]();
const docAspect = this.docWidth() / this.docHeight();
- return (docAspect > aspect) ? this.docHeight() * aspect : this.docWidth();
- }
+ return docAspect > aspect ? this.docHeight() * aspect : this.docWidth();
+ };
@computed get renderContent() {
TraceMobx();
const expandKey = this.treeViewExpandedView;
- if (["links", "annotations", "aliases", this.fieldKey].includes(expandKey)) {
- const key = (expandKey === "annotations" ? `${this.fieldKey}-` : "") + expandKey;
+ const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } };
+ if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) {
+ const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
+ const sortKeys = Object.keys(sortings);
+ const curSortIndex = Math.max(
+ 0,
+ sortKeys.findIndex(val => val === sorting)
+ );
+ const key = (expandKey === 'annotations' ? `${this.fieldKey}-` : '') + expandKey;
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
// if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't),
// then the modification would be done here
const ordering = StrCast(this.doc.treeViewSortCriterion);
- if (ordering === "Z") {
+ if (ordering === TreeSort.Zindex) {
const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
docs.push(doc);
- docs.sort((a, b) => NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1).forEach((d, i) => d.zIndex = i);
+ docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i));
}
const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
added && (doc.context = this.doc.context);
return added;
};
const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
- const docs = expandKey === "aliases" ? this.childAliases : expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs;
- let downX = 0, downY = 0;
- const sortings = ["up", "down", "Z", undefined];
- const curSort = Math.max(0, sortings.indexOf(Cast(this.doc.treeViewSortCriterion, "string", null)));
- return <ul key={expandKey + "more"} title={"sort: " + sortings[curSort]} className={this.doc.treeViewHideTitle ? "no-indent" : ""}
- onPointerDown={e => { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }}
- onClick={(e) => {
- if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
- !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortings[(curSort + 1) % sortings.length]);
- e.stopPropagation();
- }
- }}>
- {!docs ? (null) :
- TreeView.GetChildElements(docs, this.props.treeView, this, this.layoutDoc,
- this.dataDoc, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move,
- StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform,
- this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields,
- [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems())}
- </ul >;
- } else if (this.treeViewExpandedView === "fields") {
- return <ul key={this.doc[Id] + this.doc.title}>
- <div style={{ display: "inline-block" }} >
- {this.expandedField}
- </div>
- </ul>;
+ const docs = expandKey === 'aliases' ? this.childAliases : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs;
+ let downX = 0,
+ downY = 0;
+ return (
+ <>
+ {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
+ <div className={'treeView-sorting'} style={{ background: sortings[sorting]?.color }}>
+ {sortings[sorting]?.label}
+ </div>
+ )}
+ <ul
+ key={expandKey + 'more'}
+ title="click to change sort order"
+ className={this.doc.treeViewHideTitle ? 'no-indent' : ''}
+ onPointerDown={e => {
+ downX = e.clientX;
+ downY = e.clientY;
+ e.stopPropagation();
+ }}
+ onClick={e => {
+ if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+ !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+ e.stopPropagation();
+ }
+ }}>
+ {!docs
+ ? null
+ : TreeView.GetChildElements(
+ docs,
+ this.props.treeView,
+ this,
+ this.layoutDoc,
+ this.dataDoc,
+ this.props.containerCollection,
+ this.props.prevSibling,
+ addDoc,
+ remDoc,
+ this.move,
+ StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType,
+ this.props.addDocTab,
+ this.titleStyleProvider,
+ this.props.ScreenToLocalTransform,
+ this.props.isContentActive,
+ this.props.panelWidth,
+ this.props.renderDepth,
+ this.props.treeViewHideHeaderFields,
+ [...this.props.renderedIds, this.doc[Id]],
+ this.props.onCheckedClick,
+ this.props.onChildClick,
+ this.props.skipFields,
+ false,
+ this.props.whenChildContentsActiveChanged,
+ this.props.dontRegisterView,
+ emptyFunction,
+ emptyFunction,
+ this.childContextMenuItems(),
+ // TODO: [AL] add these
+ this.props.AddToMap,
+ this.props.RemFromMap,
+ this.props.hierarchyIndex
+ )}
+ </ul>
+ </>
+ );
+ } else if (this.treeViewExpandedView === 'fields') {
+ return (
+ <ul key={this.doc[Id] + this.doc.title}>
+ <div style={{ display: 'inline-block' }}>{this.expandedField}</div>
+ </ul>
+ );
}
- return <ul onPointerDown={e => { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, returnFalse)}</ul>; // "layout"
+ return (
+ <ul
+ onPointerDown={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}>
+ {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}
+ </ul>
+ ); // "layout"
}
- get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); }
+ get onCheckedClick() {
+ return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick);
+ }
@action
bulletClick = (e: React.MouseEvent) => {
if (this.onCheckedClick) {
- this.onCheckedClick?.script.run({
- this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
- heading: this.props.containerCollection.title,
- checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? "remove" : "check",
- containingTreeView: this.props.treeView.props.Document,
- }, console.log);
+ this.onCheckedClick?.script.run(
+ {
+ this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
+ heading: this.props.containerCollection.title,
+ checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check',
+ containingTreeView: this.props.treeView.props.Document,
+ },
+ console.log
+ );
} else {
this.treeViewOpen = !this.treeViewOpen;
}
e.stopPropagation();
- }
+ };
@computed get renderBullet() {
TraceMobx();
- const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ":open" : "")) || "question";
- const checked = this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined;
- return <div className={`bullet${this.props.treeView.outlineMode ? "-outline" : ""}`} key={"bullet"}
- title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
- onClick={this.bulletClick}
- style={this.props.treeView.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) } : {
- color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"),
- opacity: checked === "unchecked" ? undefined : 0.4
- }}>
- {this.props.treeView.outlineMode ?
- !(this.doc.text as RichTextField)?.Text ? (null) :
- <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? "fas" : "far", "circle"]} /> :
- <div className="treeView-bulletIcons" >
- <div className={`treeView-${this.onCheckedClick ? "checkIcon" : "expandIcon"}`}>
- <FontAwesomeIcon size="sm" icon={
- checked === "check" ? "check" :
- checked === "x" ? "times" :
- checked === "unchecked" ? "square" :
- !this.treeViewOpen ? "caret-right" : "caret-down"} />
+ const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : '')) || 'question';
+ const checked = this.onCheckedClick ? this.doc.treeViewChecked ?? 'unchecked' : undefined;
+ return (
+ <div
+ className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
+ key={'bullet'}
+ title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'}
+ onClick={this.bulletClick}
+ style={
+ this.props.treeView.outlineMode
+ ? {
+ opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity),
+ }
+ : {
+ pointerEvents: this.props.isContentActive() ? 'all' : undefined,
+ opacity: checked === 'unchecked' || typeof iconType !== 'string' ? undefined : 0.4,
+ color: StrCast(this.doc.color, checked === 'unchecked' ? 'white' : 'inherit'),
+ }
+ }>
+ {this.props.treeView.outlineMode ? (
+ !(this.doc.text as RichTextField)?.Text ? null : (
+ <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />
+ )
+ ) : (
+ <div className="treeView-bulletIcons">
+ <div className={`treeView-${this.onCheckedClick ? 'checkIcon' : 'expandIcon'}`}>
+ <FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} />
+ </div>
+ {this.onCheckedClick ? null : typeof iconType === 'string' ? <FontAwesomeIcon icon={iconType as IconProp} /> : iconType}
</div>
- {this.onCheckedClick ? (null) : <FontAwesomeIcon icon={iconType} />}
- </div>
- }
- </div>;
+ )}
+ </div>
+ );
}
@computed get validExpandViewTypes() {
- if (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) {
- return [this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : "layout"];
- }
- const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : "";
- const links = () => DocListCast(this.doc.links).length ? "links" : "";
- const data = () => this.childDocs ? this.fieldKey : "";
- const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases";
- const fields = () => Doc.UserDoc().noviceMode ? "" : "fields";
- const layout = this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
+ const annos = () => (DocListCast(this.doc[this.fieldKey + '-annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : '');
+ const links = () => (DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? 'links' : '');
+ const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : '');
+ const aliases = () => (this.props.treeView.dashboardMode ? '' : 'aliases');
+ const fields = () => (Doc.noviceMode ? '' : 'fields');
+ const layout = Doc.noviceMode || this.doc.viewType === CollectionViewType.Docking ? [] : ['layout'];
return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
}
@action
@@ -510,46 +719,68 @@ export class TreeView extends React.Component<TreeViewProps> {
this.doc.treeViewExpandedView = next(this.validExpandViewTypes);
}
this.treeViewOpen = true;
- }
+ };
@computed get headerElements() {
- return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? (null)
- : <>
- {this.doc.hideContextMenu ? (null) : <FontAwesomeIcon key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} />}
- {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? (null) :
- <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
+ return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : (
+ <>
+ {this.doc.hideContextMenu ? null : (
+ <FontAwesomeIcon
+ title="context menu"
+ key="bars"
+ icon="bars"
+ size="sm"
+ onClick={e => {
+ this.showContextMenu(e);
+ e.stopPropagation();
+ }}
+ />
+ )}
+ {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? null : (
+ <span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
{this.treeViewExpandedView}
- </span>}
- </>;
+ </span>
+ )}
+ </>
+ );
}
showContextMenu = (e: React.MouseEvent) => {
DocumentViewInternal.SelectAfterContextMenu = false;
simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
DocumentViewInternal.SelectAfterContextMenu = true;
- }
+ };
contextMenuItems = () => {
- const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, icon: "folder-plus", label: "New Folder" };
- const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: "any" })!, icon: "folder-plus", label: "Delete" };
+ const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
+ const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' };
const folderOp = this.childDocs?.length ? [makeFolder] : [];
- const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: "copy", label: "Open Alias" };
- const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: "eye", label: "Focus or Open" };
- return [...this.props.contextMenuItems.filter(mi => !mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result), ... (this.doc.isFolder ? folderOp :
- Doc.IsSystem(this.doc) ? [] :
- this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
- [openAlias, makeFolder] :
- this.doc.viewType === CollectionViewType.Docking ? [] :
- [deleteItem, openAlias, focusDoc])];
- }
+ const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' };
+ const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
+ return [
+ ...this.props.contextMenuItems.filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
+ ...(this.doc.isFolder
+ ? folderOp
+ : Doc.IsSystem(this.doc)
+ ? []
+ : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
+ ? [openAlias, makeFolder]
+ : this.doc.viewType === CollectionViewType.Docking
+ ? []
+ : [deleteItem, openAlias, focusDoc]),
+ ];
+ };
childContextMenuItems = () => {
const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
const icons = StrListCast(this.doc.childContextMenuIcons);
return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
- }
- onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
+ };
+
+ onChildClick = () => {
+ return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
+ };
- onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
+ onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document);
ignoreEvent = (e: any) => {
@@ -557,39 +788,59 @@ export class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
e.preventDefault();
}
- }
- titleStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ };
+ titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
- switch (property.split(":")[0]) {
- case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1;
- case StyleProp.BackgroundColor: return this.selected ? "#7089bb" : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
- case StyleProp.DocContents: return this.props.treeView.outlineMode ? (null) :
- <div className="treeView-label" style={{ // just render a title for a tree view label (identified by treeViewDoc being set in 'props')
- maxWidth: props?.PanelWidth() || undefined,
- background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
- }}>
- {StrCast(doc?.title)}
- </div>;
- default: return this.props?.treeView?.props.styleProvider?.(doc, props, property);
+ switch (property.split(':')[0]) {
+ case StyleProp.Opacity:
+ return this.props.treeView.outlineMode ? undefined : 1;
+ case StyleProp.BackgroundColor:
+ return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+ case StyleProp.DocContents:
+ return this.props.treeView.outlineMode ? null : (
+ <div
+ className="treeView-label"
+ style={{
+ // just render a title for a tree view label (identified by treeViewDoc being set in 'props')
+ maxWidth: props?.PanelWidth() || undefined,
+ background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
+ }}>
+ {StrCast(doc?.title)}
+ </div>
+ );
+ default:
+ return this.props?.treeView?.props.styleProvider?.(doc, props, property);
}
- }
- embeddedStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
- if (property.startsWith(StyleProp.Decorations)) return (null);
+ };
+ embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+ if (property.startsWith(StyleProp.Decorations)) return null;
return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
- }
- onKeyDown = (e: React.KeyboardEvent) => {
- if (this.doc.treeViewHideHeader || this.props.treeView.outlineMode) {
- e.stopPropagation();
- e.preventDefault();
+ };
+ onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
switch (e.key) {
- case "Tab": setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
- return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab");
- case "Backspace": return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc);
- case "Enter": return UndoManager.RunInBatch(this.makeTextCollection, "bullet");
+ case 'Tab':
+ e.stopPropagation?.();
+ e.preventDefault?.();
+ setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
+ UndoManager.RunInBatch(() => (e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true)), 'tab');
+ return true;
+ case 'Backspace':
+ if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) {
+ e.stopPropagation?.();
+ e.preventDefault?.();
+ return true;
+ }
+ break;
+ case 'Enter':
+ e.stopPropagation?.();
+ e.preventDefault?.();
+ return UndoManager.RunInBatch(this.makeTextCollection, 'bullet');
}
}
- }
+ return false;
+ };
titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth()));
return18 = () => 18;
@@ -599,28 +850,32 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed
get renderTitle() {
TraceMobx();
- const view = this._editTitle ? <EditableView key="_editTitle"
- oneLine={true}
- display={"inline-block"}
- editing={this._editTitle}
- background={"#7089bb"}
- contents={StrCast(this.doc.title)}
- height={12}
- sizeToContent={true}
- fontSize={12}
- GetValue={() => StrCast(this.doc.title)}
- OnTab={undoBatch((shift?: boolean) => {
- if (!shift) this.props.indentDocument?.(true);
- else this.props.outdentDocument?.(true);
- })}
- OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))}
- OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()}
- SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
- Doc.SetInPlace(this.doc, "title", value, false);
- this.props.treeView.outlineMode && enterKey && this.makeTextCollection();
- })}
- />
- : <DocumentView key="title"
+ const view = this._editTitle ? (
+ <EditableView
+ key="_editTitle"
+ oneLine={true}
+ display={'inline-block'}
+ editing={this._editTitle}
+ background={'#7089bb'}
+ contents={StrCast(this.doc.title)}
+ height={12}
+ sizeToContent={true}
+ fontSize={12}
+ GetValue={() => StrCast(this.doc.title)}
+ OnTab={undoBatch((shift?: boolean) => {
+ if (!shift) this.props.indentDocument?.(true);
+ else this.props.outdentDocument?.(true);
+ })}
+ OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))}
+ OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()}
+ SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
+ Doc.SetInPlace(this.doc, 'title', value, false);
+ this.props.treeView.outlineMode && enterKey && this.makeTextCollection();
+ })}
+ />
+ ) : (
+ <DocumentView
+ key="title"
ref={action((r: any) => {
this._docRef = r ? r : undefined;
if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) {
@@ -635,7 +890,6 @@ export class TreeView extends React.Component<TreeViewProps> {
hideDecorationTitle={this.props.treeView.outlineMode}
hideResizeHandles={this.props.treeView.outlineMode}
styleProvider={this.titleStyleProvider}
- layerProvider={returnTrue}
docViewPath={returnEmptyDoclist}
treeViewDoc={this.props.treeView.props.Document}
addDocument={undefined}
@@ -667,148 +921,171 @@ export class TreeView extends React.Component<TreeViewProps> {
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={this.props.treeView.props.Document}
- ContentScaling={returnOne}
- />;
-
- const buttons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ":afterHeader" : ""));
- return <>
- <div className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? "-system" : ""}`} ref={this._tref} title="click to edit title. Double Click or Drag to Open"
- style={{
- fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? "bold" : undefined,
- textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined,
- outline: this.doc === CurrentUserUtils.ActiveDashboard ? "dashed 1px #06123232" : undefined,
- pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined
- }} >
- {view}
- </div >
- <div className="treeView-rightButtons">
- {buttons} {/* hide and lock buttons */}
- {this.headerElements}
- </div>
- </>;
- }
+ />
+ );
- renderBulletHeader = (contents: JSX.Element, editing: boolean) => {
- return <>
- <div className={`treeView-header` + (editing ? "-editing" : "")} key="titleheader"
- style={{ width: "max-content" }}
- ref={this._header}
- onClick={this.ignoreEvent}
- onPointerDown={this.ignoreEvent}
- onPointerEnter={this.onPointerEnter}
- onPointerLeave={this.onPointerLeave}>
- {contents}
- </div>
- {this.renderBorder}
- </>;
+ const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : ''));
+ return (
+ <>
+ <div
+ className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? '-system' : ''}`}
+ ref={this._tref}
+ title="click to edit title. Double Click or Drag to Open"
+ style={{
+ fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined,
+ textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined,
+ outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
+ pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+ }}>
+ {view}
+ </div>
+ <div className="treeView-rightButtons">
+ {buttons} {/* hide and lock buttons */}
+ {this.headerElements}
+ </div>
+ </>
+ );
}
+ renderBulletHeader = (contents: JSX.Element, editing: boolean) => {
+ return (
+ <>
+ <div
+ className={`treeView-header` + (editing ? '-editing' : '')}
+ key="titleheader"
+ style={{ width: StrCast(this.doc.treeViewHeaderWidth, 'max-content') }}
+ ref={this._header}
+ onClick={this.ignoreEvent}
+ onPointerDown={this.ignoreEvent}
+ onPointerEnter={this.onPointerEnter}
+ onPointerLeave={this.onPointerLeave}>
+ {contents}
+ </div>
+ {this.renderBorder}
+ </>
+ );
+ };
renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
- const layout = StrCast(Doc.LayoutField(this.layoutDoc));
- const isExpandable = layout.includes(FormattedTextBox.name) || layout.includes(SliderBox.name);
+ const isExpandable = this.doc._treeViewGrowsHorizontally;
const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth;
const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight;
- return <DocumentView key={this.doc[Id]} ref={action((r: DocumentView | null) => this._dref = r)}
- Document={this.doc}
- DataDoc={undefined}
- PanelWidth={panelWidth}
- PanelHeight={panelHeight}
- NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined}
- NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined}
- LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined}
- isContentActive={isActive}
- isDocumentActive={isActive}
- styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
- hideTitle={asText}
- fitContentsToDoc={returnTrue}
- hideDecorationTitle={this.props.treeView.outlineMode}
- hideResizeHandles={this.props.treeView.outlineMode}
- focus={this.refocus}
- ContentScaling={returnOne}
- hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
- dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
- ScreenToLocalTransform={this.docTransform}
- renderDepth={this.props.renderDepth + 1}
- treeViewDoc={this.props.treeView?.props.Document}
- rootSelected={returnTrue}
- layerProvider={returnTrue}
- docViewPath={this.props.treeView.props.docViewPath}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.containerCollection}
- ContainingCollectionView={undefined}
- addDocument={this.props.addDocument}
- moveDocument={this.move}
- removeDocument={this.props.removeDoc}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.treeView.props.pinToPres}
- disableDocBrushing={this.props.treeView.props.disableDocBrushing}
- bringToFront={returnFalse}
- />;
- }
+ return (
+ <DocumentView
+ key={this.doc[Id]}
+ ref={action((r: DocumentView | null) => (this._dref = r))}
+ Document={this.doc}
+ DataDoc={undefined}
+ PanelWidth={panelWidth}
+ PanelHeight={panelHeight}
+ NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined}
+ NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined}
+ LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
+ LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
+ isContentActive={isActive}
+ isDocumentActive={isActive}
+ styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
+ hideTitle={asText}
+ fitContentsToBox={returnTrue}
+ hideDecorationTitle={this.props.treeView.outlineMode}
+ hideResizeHandles={this.props.treeView.outlineMode}
+ onClick={this.onChildClick}
+ focus={this.refocus}
+ onKey={this.onKeyDown}
+ hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
+ dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+ ScreenToLocalTransform={this.docTransform}
+ renderDepth={this.props.renderDepth + 1}
+ treeViewDoc={this.props.treeView?.props.Document}
+ rootSelected={returnTrue}
+ docViewPath={this.props.treeView.props.docViewPath}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={this.props.containerCollection}
+ ContainingCollectionView={undefined}
+ addDocument={this.props.addDocument}
+ moveDocument={this.move}
+ removeDocument={this.props.removeDoc}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.treeView.props.pinToPres}
+ disableDocBrushing={this.props.treeView.props.disableDocBrushing}
+ bringToFront={returnFalse}
+ />
+ );
+ };
// renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views.
@computed get renderTitleAsHeader() {
- return <>
- {this.renderBullet}
- {this.renderTitle}
- </>;
+ return (
+ <>
+ {this.renderBullet}
+ {this.renderTitle}
+ </>
+ );
}
// renders the document in the header field instead of a text proxy.
- @computed get renderDocumentAsHeader() {
- return <>
- {this.renderBullet}
- {this.renderEmbeddedDocument(true, this.props.isContentActive)}
- </>;
- }
+ renderDocumentAsHeader = (asText: boolean) => {
+ return (
+ <>
+ {this.renderBullet}
+ {this.renderEmbeddedDocument(asText, this.props.isContentActive)}
+ </>
+ );
+ };
@computed get renderBorder() {
- const sorting = this.doc[`${this.fieldKey}-sortCriterion`];
- return <div className={`treeView-border${this.props.treeView.outlineMode ? "outline" : ""}`}
- style={{ borderColor: sorting === undefined ? undefined : sorting === "up" ? "crimson" : sorting === "down" ? "blue" : "green" }}>
- {!this.treeViewOpen ? (null) : this.renderContent}
- </div>;
+ const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
+ const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } };
+ return (
+ <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
+ {!this.treeViewOpen ? null : this.renderContent}
+ </div>
+ );
}
onTreeDrop = (de: React.DragEvent) => {
const pt = [de.clientX, de.clientY];
const rect = this._header.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
- const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length);
-
- const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, "copy", undefined, false));
- }
+ const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length);
+ const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false));
+ };
render() {
TraceMobx();
- const hideTitle = this.doc.treeViewHideHeader || this.props.treeView.outlineMode;
- return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
- <div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`}
+ const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode;
+ return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (
+ '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
+ ) : (
+ <div
+ className={`treeView-container${this.props.isContentActive() ? '-active' : ''}`}
ref={this.createTreeDropTarget}
onDrop={this.onTreeDrop}
//onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
- onKeyDown={this.onKeyDown}>
+ // onKeyDown={this.onKeyDown}
+ >
<li className="collection-child">
- {hideTitle && this.doc.type !== DocumentType.RTF ?
- this.renderEmbeddedDocument(false, returnFalse) :
- this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader : this.renderTitleAsHeader, this._editTitle)}
+ {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader // should test for prop 'treeViewRenderDocWithBulletAsHeader"
+ ? this.renderEmbeddedDocument(false, returnFalse)
+ : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
</li>
- </div>;
+ </div>
+ );
}
public static sortDocs(childDocs: Doc[], criterion: string | undefined) {
const docs = childDocs.slice();
- if (criterion) {
+ if (criterion !== TreeSort.None) {
const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => {
const reN = /[0-9]*$/;
- const aA = a.replace(reN, ""); // get rid of trailing numbers
- const bA = b.replace(reN, "");
- if (aA === bA) { // if header string matches, then compare numbers numerically
+ const aA = a.replace(reN, '') ? a.replace(reN, '') : +a; // get rid of trailing numbers
+ const bA = b.replace(reN, '') ? b.replace(reN, '') : +b;
+ if (aA === bA) {
+ // if header string matches, then compare numbers numerically
const aN = parseInt(a.match(reN)![0], 10);
const bN = parseInt(b.match(reN)![0], 10);
return aN === bN ? 0 : aN > bN ? 1 : -1;
@@ -817,11 +1094,11 @@ export class TreeView extends React.Component<TreeViewProps> {
}
};
docs.sort(function (d1, d2): 0 | 1 | -1 {
- const a = (criterion === "up" ? d2 : d1);
- const b = (criterion === "up" ? d1 : d2);
- const first = a[criterion === "Z" ? "zIndex" : "title"];
- const second = b[criterion === "Z" ? "zIndex" : "title"];
- if (typeof first === 'number' && typeof second === 'number') return (first - second) > 0 ? 1 : -1;
+ const a = criterion === TreeSort.Up ? d2 : d1;
+ const b = criterion === TreeSort.Up ? d1 : d2;
+ const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title'];
+ const second = b[criterion === TreeSort.Zindex ? 'zIndex' : 'title'];
+ if (typeof first === 'number' && typeof second === 'number') return first - second > 0 ? 1 : -1;
if (typeof first === 'string' && typeof second === 'string') return sortAlphaNum(first, second);
return criterion ? 1 : -1;
});
@@ -857,74 +1134,97 @@ export class TreeView extends React.Component<TreeViewProps> {
dontRegisterView: boolean | undefined,
observerHeight: (ref: any) => void,
unobserveHeight: (ref: any) => void,
- contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[])
+ contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[],
+ // TODO: [AL] add these
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+ hierarchyIndex?: number[]
) {
const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
}
- const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion));
+ const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None));
const rowWidth = () => panelWidth() - treeBulletWidth();
const treeViewRefs = new Map<Doc, TreeView | undefined>();
- return docs.filter(child => child instanceof Doc).map((child, i) => {
- const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
- if (!pair.layout || pair.data instanceof Promise) {
- return (null);
- }
-
- const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
- if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return;
- const fieldKey = Doc.LayoutFieldKey(newParent);
- if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
- remove(child);
- FormattedTextBox.SelectOnLoad = child[Id];
- TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
- Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
- newParent.treeViewOpen = true;
- child.context = treeView.Document;
+ return docs
+ .filter(child => child instanceof Doc)
+ .map((child, i) => {
+ const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return null;
}
- };
- const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
- const outdent = parentCollectionDoc?._viewType !== CollectionViewType.Tree ? undefined : ((editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined));
- const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
- const childLayout = Doc.Layout(pair.layout);
- const rowHeight = () => {
- const aspect = Doc.NativeAspect(childLayout);
- return (aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]());
- };
- return <TreeView key={child[Id]} ref={r => treeViewRefs.set(child, r ? r : undefined)}
- document={pair.layout}
- dataDoc={pair.data}
- containerCollection={containerCollection}
- prevSibling={docs[i]}
- treeView={treeView}
- indentDocument={indent}
- outdentDocument={outdent}
- onCheckedClick={onCheckedClick}
- onChildClick={onChildClick}
- renderDepth={renderDepth}
- removeDoc={StrCast(containerCollection.freezeChildren).includes("remove") ? undefined : remove}
- addDocument={addDocument}
- styleProvider={styleProvider}
- panelWidth={rowWidth}
- panelHeight={rowHeight}
- dontRegisterView={dontRegisterView}
- moveDocument={move}
- dropAction={dropAction}
- addDocTab={addDocTab}
- ScreenToLocalTransform={screenToLocalXf}
- isContentActive={isContentActive}
- treeViewHideHeaderFields={treeViewHideHeaderFields}
- renderedIds={renderedIds}
- skipFields={skipFields}
- firstLevel={firstLevel}
- whenChildContentsActiveChanged={whenChildContentsActiveChanged}
- parentTreeView={parentTreeView}
- observeHeight={observerHeight}
- unobserveHeight={unobserveHeight}
- contextMenuItems={contextMenuItems}
- />;
- });
+
+ const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
+ if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return;
+ const fieldKey = Doc.LayoutFieldKey(newParent);
+ if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
+ remove(child);
+ FormattedTextBox.SelectOnLoad = child[Id];
+ TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
+ Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
+ newParent.treeViewOpen = true;
+ child.context = treeView.Document;
+ }
+ };
+ const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
+ const outdent =
+ parentCollectionDoc?._viewType !== CollectionViewType.Tree
+ ? undefined
+ : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
+ const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
+ const childLayout = Doc.Layout(pair.layout);
+ const rowHeight = () => {
+ const aspect = Doc.NativeAspect(childLayout);
+ return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
+ };
+ return (
+ <TreeView
+ key={child[Id]}
+ ref={r => treeViewRefs.set(child, r ? r : undefined)}
+ document={pair.layout}
+ dataDoc={pair.data}
+ containerCollection={containerCollection}
+ prevSibling={docs[i]}
+ // TODO: [AL] add these
+ hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
+ AddToMap={AddToMap}
+ RemFromMap={RemFromMap}
+ treeView={treeView}
+ indentDocument={indent}
+ outdentDocument={outdent}
+ onCheckedClick={onCheckedClick}
+ onChildClick={onChildClick}
+ renderDepth={renderDepth}
+ removeDoc={StrCast(containerCollection.freezeChildren).includes('remove') ? undefined : remove}
+ addDocument={addDocument}
+ styleProvider={styleProvider}
+ panelWidth={rowWidth}
+ panelHeight={rowHeight}
+ dontRegisterView={dontRegisterView}
+ moveDocument={move}
+ dropAction={dropAction}
+ addDocTab={addDocTab}
+ ScreenToLocalTransform={screenToLocalXf}
+ isContentActive={isContentActive}
+ treeViewHideHeaderFields={treeViewHideHeaderFields}
+ renderedIds={renderedIds}
+ skipFields={skipFields}
+ firstLevel={firstLevel}
+ whenChildContentsActiveChanged={whenChildContentsActiveChanged}
+ parentTreeView={parentTreeView}
+ observeHeight={observerHeight}
+ unobserveHeight={unobserveHeight}
+ contextMenuItems={contextMenuItems}
+ />
+ );
+ });
}
-} \ No newline at end of file
+}
+
+ScriptingGlobals.add(function TreeView_addNewFolder() {
+ TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
+ const opts = { title: 'Untitled folder', _stayInCollection: true, isFolder: true };
+ return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
+});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9fed82dae..3d85d32a0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,13 +1,12 @@
-import { Doc, Field, FieldResult, HeightSym, WidthSym } from "../../../../fields/Doc";
-import { Id, ToString } from "../../../../fields/FieldSymbols";
-import { ObjectField } from "../../../../fields/ObjectField";
-import { RefField } from "../../../../fields/RefField";
-import { listSpec } from "../../../../fields/Schema";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { aggregateBounds } from "../../../../Utils";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import React = require("react");
-import { ColorScheme } from "../../../util/SettingsManager";
+import { Doc, Field, FieldResult, HeightSym, WidthSym } from '../../../../fields/Doc';
+import { Id, ToString } from '../../../../fields/FieldSymbols';
+import { ObjectField } from '../../../../fields/ObjectField';
+import { RefField } from '../../../../fields/RefField';
+import { listSpec } from '../../../../fields/Schema';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { aggregateBounds } from '../../../../Utils';
+import { ColorScheme } from '../../../util/SettingsManager';
+import React = require('react');
export interface ViewDefBounds {
type: string;
@@ -25,7 +24,7 @@ export interface ViewDefBounds {
color?: string;
opacity?: number;
replica?: string;
- pair?: { layout: Doc, data?: Doc };
+ pair?: { layout: Doc; data?: Doc };
}
export interface PoolData {
@@ -40,7 +39,7 @@ export interface PoolData {
transition?: string;
highlight?: boolean;
replica: string;
- pair: { layout: Doc, data?: Doc };
+ pair: { layout: Doc; data?: Doc };
}
export interface ViewDefResult {
@@ -48,7 +47,7 @@ export interface ViewDefResult {
bounds?: ViewDefBounds;
}
function toLabel(target: FieldResult<Field>) {
- if (typeof target === "number" || Number(target)) {
+ if (typeof target === 'number' || Number(target)) {
const truncated = Number(Number(target).toFixed(0));
const precise = Number(Number(target).toFixed(2));
return truncated === precise ? Number(target).toFixed(0) : Number(target).toFixed(2);
@@ -60,16 +59,16 @@ function toLabel(target: FieldResult<Field>) {
}
/**
* Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
- *
+ *
* @param {String} text The text to be rendered.
* @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
- *
+ *
* @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
*/
function getTextWidth(text: string, font: string): number {
// re-use canvas object for better performance
- const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
- const context = canvas.getContext("2d");
+ const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement('canvas'));
+ const context = canvas.getContext('2d');
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
@@ -81,14 +80,7 @@ interface PivotColumn {
filters: string[];
}
-export function computerPassLayout(
- poolData: Map<string, PoolData>,
- pivotDoc: Doc,
- childPairs: { layout: Doc, data?: Doc }[],
- panelDim: number[],
- viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
- engineProps: any
-) {
+export function computerPassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
const docMap = new Map<string, PoolData>();
childPairs.forEach(({ layout, data }, i) => {
docMap.set(layout[Id], {
@@ -97,58 +89,49 @@ export function computerPassLayout(
width: layout[WidthSym](),
height: layout[HeightSym](),
pair: { layout, data },
- replica: ""
+ replica: '',
});
});
return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
}
-export function computerStarburstLayout(
- poolData: Map<string, PoolData>,
- pivotDoc: Doc,
- childPairs: { layout: Doc, data?: Doc }[],
- panelDim: number[],
- viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
- engineProps: any
-) {
+export function computerStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+ const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
const docMap = new Map<string, PoolData>();
- const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
- const docScale = NumCast(pivotDoc._starburstDocScale);
- const docSize = docScale * 100; // assume a icon sized at 100
- const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
+ const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75
+ const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
+ const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
childPairs.forEach(({ layout, data }, i) => {
- const deg = i / childPairs.length * Math.PI * 2;
+ const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75
+ const deg = (i / childPairs.length) * Math.PI * 2;
docMap.set(layout[Id], {
- x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2,
- y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2,
- width: docScale * layout[WidthSym](),
- height: docScale * layout[HeightSym](),
+ x: Math.cos(deg) * burstRadius[0] - docSize / 2,
+ y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2,
+ width: docSize, //layout[WidthSym](),
+ height: (docSize * layout[HeightSym]()) / layout[WidthSym](),
+ zIndex: NumCast(layout.zIndex),
pair: { layout, data },
- replica: ""
+ replica: '',
});
});
- const divider = { type: "div", color: "transparent", x: -burstRadius[0] / 3, y: 0, width: 15, height: 15, payload: undefined };
+ const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
}
-
-export function computePivotLayout(
- poolData: Map<string, PoolData>,
- pivotDoc: Doc,
- childPairs: { layout: Doc, data?: Doc }[],
- panelDim: number[],
- viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
- engineProps: any
-) {
+export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
const docMap = new Map<string, PoolData>();
- const fieldKey = "data";
+ const fieldKey = 'data';
const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
let nonNumbers = 0;
- const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField);
+ const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author';
childPairs.map(pair => {
- const lval = pivotFieldKey === "#" || pivotFieldKey === "tags" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
- Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+ const lval =
+ pivotFieldKey === '#' || pivotFieldKey === 'tags'
+ ? Array.from(Object.keys(Doc.GetProto(pair.layout)))
+ .filter(k => k.startsWith('#'))
+ .map(k => k.substring(1))
+ : Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
const num = toNumber(pair.layout[pivotFieldKey]);
if (num === undefined || Number.isNaN(num)) {
@@ -164,7 +147,7 @@ export function computePivotLayout(
} else if (val) {
!pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
pivotColumnGroups.get(val)!.docs.push(pair.layout);
- pivotColumnGroups.get(val)!.replicas.push("");
+ pivotColumnGroups.get(val)!.replicas.push('');
} else {
docMap.set(pair.layout[Id], {
x: 0,
@@ -173,11 +156,11 @@ export function computePivotLayout(
width: 0,
height: 0,
pair,
- replica: ""
+ replica: '',
});
}
});
- const pivotNumbers = nonNumbers / childPairs.length < .1;
+ const pivotNumbers = nonNumbers / childPairs.length < 0.1;
if (pivotColumnGroups.size > 10) {
const arrayofKeys = Array.from(pivotColumnGroups.keys());
const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort();
@@ -194,9 +177,11 @@ export function computePivotLayout(
}
}
}
- const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
+ const fontSize = NumCast(pivotDoc[fieldKey + '-timelineFontSize'], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`;
- const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number);
+ const textlen = Array.from(pivotColumnGroups.keys())
+ .map(c => getTextWidth(toLabel(c), desc))
+ .reduce((p, c) => Math.max(p, c), 0 as number);
const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
@@ -220,7 +205,7 @@ export function computePivotLayout(
const groupNames: ViewDefBounds[] = [];
const expander = 1.05;
- const gap = .15;
+ const gap = 0.15;
const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
let x = 0;
const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort();
@@ -230,14 +215,14 @@ export function computePivotLayout(
let xCount = 0;
const text = toLabel(key);
groupNames.push({
- type: "text",
+ type: 'text',
text,
x,
y: pivotAxisWidth,
width: pivotAxisWidth * expander * numCols,
height: max_text,
fontSize,
- payload: val
+ payload: val,
});
val.docs.forEach((doc, i) => {
const layoutDoc = Doc.Layout(doc);
@@ -247,13 +232,13 @@ export function computePivotLayout(
hgt = pivotAxisWidth;
wid = (Doc.NativeAspect(layoutDoc) || 1) * pivotAxisWidth;
}
- docMap.set(doc[Id] + (val.replicas || ""), {
- x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
+ docMap.set(doc[Id] + (val.replicas || ''), {
+ x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? ((numCols - val.docs.length) * pivotAxisWidth) / 2 : 0),
y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
height: hgt,
pair: { layout: doc },
- replica: val.replicas[i]
+ replica: val.replicas[i],
});
xCount++;
if (xCount >= numCols) {
@@ -264,8 +249,15 @@ export function computePivotLayout(
x += pivotAxisWidth * (numCols * expander + gap);
});
- const dividers = sortedPivotKeys.map((key, i) =>
- ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+ const dividers = sortedPivotKeys.map((key, i) => ({
+ type: 'div',
+ color: 'lightGray',
+ x: i * pivotAxisWidth * (numCols * expander + gap) - (pivotAxisWidth * (expander - 1)) / 2,
+ y: -maxColHeight + pivotAxisWidth,
+ width: pivotAxisWidth * numCols * expander,
+ height: maxColHeight,
+ payload: pivotColumnGroups.get(key)!.filters,
+ }));
groupNames.push(...dividers);
return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
}
@@ -274,24 +266,17 @@ function toNumber(val: FieldResult<Field>) {
return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
}
-export function computeTimelineLayout(
- poolData: Map<string, PoolData>,
- pivotDoc: Doc,
- childPairs: { layout: Doc, data?: Doc }[],
- panelDim: number[],
- viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
- engineProps?: any
-) {
- const fieldKey = "data";
+export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps?: any) {
+ const fieldKey = 'data';
const pivotDateGroups = new Map<number, Doc[]>();
const docMap = new Map<string, PoolData>();
const groupNames: ViewDefBounds[] = [];
const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
- const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
- const curTimeSpan = Cast(pivotDoc[fieldKey + "-timelineSpan"], "number", null);
- const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTime && (curTime - curTimeSpan);
- const maxTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTime && (curTime + curTimeSpan);
- const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
+ const curTime = toNumber(pivotDoc[fieldKey + '-timelineCur']);
+ const curTimeSpan = Cast(pivotDoc[fieldKey + '-timelineSpan'], 'number', null);
+ const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMinReq'], 'number', null) : curTime && curTime - curTimeSpan;
+ const maxTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMaxReq'], 'number', null) : curTime && curTime + curTimeSpan;
+ const fontSize = NumCast(pivotDoc[fieldKey + '-timelineFontSize'], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
const fontHeight = panelDim[1] > 58 ? 30 : panelDim[1] / 2;
const findStack = (time: number, stack: number[]) => {
const index = stack.findIndex(val => val === undefined || val < x);
@@ -317,8 +302,8 @@ export function computeTimelineLayout(
}
}
setTimeout(() => {
- pivotDoc[fieldKey + "-timelineMin"] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime;
- pivotDoc[fieldKey + "-timelineMax"] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime;
+ pivotDoc[fieldKey + '-timelineMin'] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime;
+ pivotDoc[fieldKey + '-timelineMax'] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime;
}, 0);
if (maxTime === minTime) {
@@ -332,10 +317,10 @@ export function computeTimelineLayout(
let prevKey = Math.floor(minTime);
if (sortedKeys.length && scaling * (sortedKeys[0] - prevKey) > 25) {
- groupNames.push({ type: "text", text: toLabel(prevKey), x: x, y: 0, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: 'text', text: toLabel(prevKey), x: x, y: 0, height: fontHeight, fontSize, payload: undefined });
}
if (!sortedKeys.length && curTime !== undefined) {
- groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: 'text', text: toLabel(curTime), x: (curTime - minTime) * scaling, zIndex: 1000, color: 'orange', y: 0, height: fontHeight, fontSize, payload: undefined });
}
const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5);
@@ -343,26 +328,26 @@ export function computeTimelineLayout(
let zind = 0;
sortedKeys.forEach(key => {
if (curTime !== undefined && curTime > prevKey && curTime <= key) {
- groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key });
+ groupNames.push({ type: 'text', text: toLabel(curTime), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: 'orange', height: fontHeight, fontSize, payload: key });
}
const keyDocs = pivotDateGroups.get(key)!;
x += scaling * (key - prevKey);
const stack = findStack(x, stacking);
prevKey = key;
if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) {
- groupNames.push({ type: "text", text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: 'text', text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
}
layoutDocsAtTime(keyDocs, key);
});
if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) {
x = (curTime - minTime) * scaling;
- groupNames.push({ type: "text", text: toLabel(curTime), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: 'text', text: toLabel(curTime), x: x, y: 0, zIndex: 1000, color: 'orange', height: fontHeight, fontSize, payload: undefined });
}
if (Math.ceil(maxTime - minTime) * scaling > x + 25) {
- groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: 'text', text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
}
- const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+ const divider = { type: 'div', color: Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'dimgray' : 'black', x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
function layoutDocsAtTime(keyDocs: Doc[], key: number) {
@@ -376,13 +361,14 @@ export function computeTimelineLayout(
wid = (Doc.NativeAspect(layoutDoc) || 1) * pivotAxisWidth;
}
docMap.set(doc[Id], {
- x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
- zIndex: (curTime === key ? 1000 : zind++),
+ x: x,
+ y: (-Math.sqrt(stack) * pivotAxisWidth) / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
+ zIndex: curTime === key ? 1000 : zind++,
highlight: curTime === key,
- width: wid / (Math.max(stack, 1)),
- height: hgt / (Math.max(stack, 1)),
+ width: wid / Math.max(stack, 1),
+ height: hgt / Math.max(stack, 1),
pair: { layout: doc },
- replica: ""
+ replica: '',
});
stacking[stack] = x + pivotAxisWidth;
});
@@ -399,41 +385,51 @@ function normalizeResults(
minWidth: number,
extras: ViewDefBounds[]
): ViewDefResult[] {
- const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
+ const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds));
const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
- const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0);
- aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
- const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
- let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
+ const aggBounds = aggregateBounds(
+ extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99),
+ 0,
+ 0
+ );
+ aggBounds.r = aggBounds.x + Math.max(minWidth, aggBounds.r - aggBounds.x);
+ const width = aggBounds.r - aggBounds.x === 0 ? 1 : aggBounds.r - aggBounds.x;
+ const height = aggBounds.b - aggBounds.y === 0 ? 1 : aggBounds.b - aggBounds.y;
+ const wscale = panelDim[0] / width;
+ let scale = wscale * height > panelDim[1] ? panelDim[1] / height : wscale;
if (Number.isNaN(scale)) scale = 1;
- Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => {
- const newPosRaw = ele[1];
- if (newPosRaw) {
- const newPos = {
- x: newPosRaw.x * scale,
- y: newPosRaw.y * scale,
- z: newPosRaw.z,
- replica: newPosRaw.replica,
- highlight: newPosRaw.highlight,
- zIndex: newPosRaw.zIndex,
- width: (newPosRaw.width || 0) * scale,
- height: newPosRaw.height! * scale,
- pair: ele[1].pair
- };
- poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "all 1s", ...newPos });
- }
- });
+ Array.from(docMap.entries())
+ .filter(ele => ele[1].pair)
+ .map(ele => {
+ const newPosRaw = ele[1];
+ if (newPosRaw) {
+ const newPos = {
+ x: newPosRaw.x * scale,
+ y: newPosRaw.y * scale,
+ z: newPosRaw.z,
+ replica: newPosRaw.replica,
+ highlight: newPosRaw.highlight,
+ zIndex: newPosRaw.zIndex,
+ width: (newPosRaw.width || 0) * scale,
+ height: newPosRaw.height! * scale,
+ pair: ele[1].pair,
+ };
+ poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
+ }
+ });
- return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
- type: gname.type,
- text: gname.text,
- x: gname.x * scale,
- y: gname.y * scale,
- color: gname.color,
- width: gname.width === undefined ? undefined : gname.width * scale,
- height: gname.height === -1 ? 1 : gname.type === "text" ? Math.max(fontHeight * scale, (gname.height || 0) * scale) : (gname.height || 0) * scale,
- fontSize: gname.fontSize,
- payload: gname.payload
- })));
+ return viewDefsToJSX(
+ extras.concat(groupNames).map(gname => ({
+ type: gname.type,
+ text: gname.text,
+ x: gname.x * scale,
+ y: gname.y * scale,
+ color: gname.color,
+ width: gname.width === undefined ? undefined : gname.width * scale,
+ height: gname.height === -1 ? 1 : gname.type === 'text' ? Math.max(fontHeight * scale, (gname.height || 0) * scale) : (gname.height || 0) * scale,
+ fontSize: gname.fontSize,
+ payload: gname.payload,
+ }))
+ );
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f5a5492e3..d979ef961 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,19 +1,18 @@
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Field } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { List } from "../../../../fields/List";
-import { NumCast } from "../../../../fields/Types";
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Field } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { List } from '../../../../fields/List';
+import { Cast, NumCast } from '../../../../fields/Types';
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { LinkManager } from "../../../util/LinkManager";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { DocumentView } from "../../nodes/DocumentView";
-import "./CollectionFreeFormLinkView.scss";
-import React = require("react");
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { Colors } from "../../global/globalEnums";
-
+import { LinkManager } from '../../../util/LinkManager';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Colors } from '../../global/globalEnums';
+import { DocumentView } from '../../nodes/DocumentView';
+import './CollectionFreeFormLinkView.scss';
+import React = require('react');
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -27,37 +26,50 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
@observable _start = 0;
_anchorDisposer: IReactionDisposer | undefined;
_timeout: NodeJS.Timeout | undefined;
- componentWillUnmount() { this._anchorDisposer?.(); }
- @action timeout = action(() => (Date.now() < this._start++ + 1000) && (this._timeout = setTimeout(this.timeout, 25)));
+ componentWillUnmount() {
+ this._anchorDisposer?.();
+ }
+ @action timeout = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25)));
componentDidMount() {
- this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform()],
+ this._anchorDisposer = reaction(
+ () => [
+ this.props.A.props.ScreenToLocalTransform(),
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ this.props.B.props.ScreenToLocalTransform(),
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ ],
action(() => {
this._start = Date.now();
this._timeout && clearTimeout(this._timeout);
this._timeout = setTimeout(this.timeout, 25);
setTimeout(this.placeAnchors);
- })
- , { fireImmediately: true });
+ }),
+ { fireImmediately: true }
+ );
}
placeAnchors = () => {
const { A, B, LinkDocs } = this.props;
const linkDoc = LinkDocs[0];
if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
- setTimeout(action(() => this._opacity = 0.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
- setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const adiv = acont.length ? acont[0] : A.ContentDiv;
- const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
- const a = adiv.getBoundingClientRect();
- const b = bdiv.getBoundingClientRect();
- const { left: aleft, top: atop, width: awidth, height: aheight } = adiv.parentElement!.getBoundingClientRect();
- const { left: bleft, top: btop, width: bwidth, height: bheight } = bdiv.parentElement!.getBoundingClientRect();
+ setTimeout(
+ action(() => (this._opacity = 0.75)),
+ 0
+ ); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
+ setTimeout(
+ action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)),
+ 750
+ ); // this will unhighlight the link line.
+ const a = A.ContentDiv.getBoundingClientRect();
+ const b = B.ContentDiv.getBoundingClientRect();
+ const { left: aleft, top: atop, width: awidth, height: aheight } = A.ContentDiv.parentElement!.getBoundingClientRect();
+ const { left: bleft, top: btop, width: bwidth, height: bheight } = B.ContentDiv.parentElement!.getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2);
const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y);
// really hacky stuff to make the LinkAnchorBox display where we want it to:
- // if there's an element in the DOM with a classname containing a link anchor's id,
+ // if there's an element in the DOM with a classname containing a link anchor's id,
// then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor1 as Doc)[Id])).lastElement();
@@ -65,8 +77,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return;
if (!targetAhyperlink) {
if (linkDoc.linkAutoMove) {
- linkDoc.anchor1_x = (apt.point.x - aleft) / awidth * 100;
- linkDoc.anchor1_y = (apt.point.y - atop) / aheight * 100;
+ linkDoc.anchor1_x = ((apt.point.x - aleft) / awidth) * 100;
+ linkDoc.anchor1_y = ((apt.point.y - atop) / aheight) * 100;
}
} else {
const m = targetAhyperlink.getBoundingClientRect();
@@ -75,11 +87,13 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const mpy = mp[1] / A.props.PanelHeight();
if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
+ if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0;
+ else linkDoc.opacity = 1;
}
if (!targetBhyperlink) {
if (linkDoc.linkAutoMove) {
- linkDoc.anchor2_x = (bpt.point.x - bleft) / bwidth * 100;
- linkDoc.anchor2_y = (bpt.point.y - btop) / bheight * 100;
+ linkDoc.anchor2_x = ((bpt.point.x - bleft) / bwidth) * 100;
+ linkDoc.anchor2_y = ((bpt.point.y - btop) / bheight) * 100;
}
} else {
const m = targetBhyperlink.getBoundingClientRect();
@@ -88,97 +102,86 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const mpy = mp[1] / B.props.PanelHeight();
if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
+ if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0;
+ else linkDoc.opacity = 1;
}
- }
-
+ };
pointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, (e, down, delta) => {
- this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0];
- this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1];
- return false;
- }, emptyFunction, () => {
- // OverlayView.Instance.addElement(
- // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]}
- // showLinks={action(() => { })}
- // />, { x: 300, y: 300 });
- });
-
-
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) => {
+ this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0];
+ this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1];
+ return false;
+ },
+ emptyFunction,
+ () => {
+ // OverlayView.Instance.addElement(
+ // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]}
+ // showLinks={action(() => { })}
+ // />, { x: 300, y: 300 });
+ }
+ );
+ };
visibleY = (el: any) => {
let rect = el.getBoundingClientRect();
- const top = rect.top, height = rect.height;
+ const top = rect.top,
+ height = rect.height;
var el = el.parentNode;
while (el && el !== document.body) {
rect = el.getBoundingClientRect?.();
if (rect?.width) {
- if (top <= rect.bottom === false && getComputedStyle(el).overflow === "hidden") return rect.bottom;
+ if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom;
// Check if the element is out of view due to a container scrolling
- if ((top + height) <= rect.top && getComputedStyle(el).overflow === "hidden") return rect.top;
+ if (top + height <= rect.top && getComputedStyle(el).overflow === 'hidden') return rect.top;
}
el = el.parentNode;
}
// Check its within the document viewport
return top; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
- }
+ };
visibleX = (el: any) => {
let rect = el.getBoundingClientRect();
- const left = rect.left, width = rect.width;
+ const left = rect.left,
+ width = rect.width;
var el = el.parentNode;
while (el && el !== document.body) {
rect = el?.getBoundingClientRect();
if (rect?.width) {
- if (left <= rect.right === false && getComputedStyle(el).overflow === "hidden") return rect.right;
+ if (left <= rect.right === false && getComputedStyle(el).overflow === 'hidden') return rect.right;
// Check if the element is out of view due to a container scrolling
- if ((left + width) <= rect.left && getComputedStyle(el).overflow === "hidden") return rect.left;
+ if (left + width <= rect.left && getComputedStyle(el).overflow === 'hidden') return rect.left;
}
el = el.parentNode;
}
// Check its within the document viewport
return left; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
- }
+ };
@action
toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth > 0) {
- CurrentUserUtils.propertiesWidth = 0;
+ if (SettingsManager.propertiesWidth > 0) {
+ SettingsManager.propertiesWidth = 0;
} else {
- CurrentUserUtils.propertiesWidth = 250;
+ SettingsManager.propertiesWidth = 250;
}
- }
+ };
onClickLine = () => {
SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true);
this.toggleProperties();
- }
-
- // componentToHex = (c: number) => {
- // let hex = c.toString(16);
- // return hex.length == 1 ? "0" + hex : hex;
- // }
-
- // rgbToHex = (rgbString: string) => {
- // if (rgbString != "black") {
- // const splitString = rgbString.split(/rgb|\(|\)|,| /)
- // let values: number[] = []
- // splitString.forEach(elt => {
- // if (elt) {
- // values.push(parseInt(elt))
- // }
- // })
- // return "#" + this.componentToHex(values[0]) + this.componentToHex(values[1]) + this.componentToHex(values[2]);
- // }
- // return "#000000"
- // }
+ };
@computed.struct get renderData() {
- this._start; SnappingManager.GetIsDragging();
+ this._start;
+ SnappingManager.GetIsDragging();
const { A, B, LinkDocs } = this.props;
if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
- const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const acont = A.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+ const bcont = B.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
const adiv = acont.length ? acont[0] : A.ContentDiv;
const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
@@ -190,32 +193,18 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
if (!a.width || !b.width) return undefined;
const aDocBounds = (A.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
const bDocBounds = (B.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
- const acentX = (a.left + a.right) / 2;
- const acentY = (a.top + a.bottom) / 2;
- const bcentX = (b.left + b.right) / 2;
- const bcentY = (b.top + b.bottom) / 2;
- const pt1Arc = ((acentX - aDocBounds.left) > 0.1 && (aDocBounds.right - acentX) > 0.1) ||
- ((acentY - aDocBounds.top) > 0.1 && (aDocBounds.bottom - acentY) > 0.1);
- const pt2Arc = ((bcentX - bDocBounds.left) > 0.1 && (bDocBounds.right - bcentX) > 0.1) ||
- ((bcentY - bDocBounds.top) > 0.1 && (bDocBounds.bottom - bcentY) > 0.1);
- const atop2 = this.visibleY(adiv);
- const btop2 = this.visibleY(bdiv);
const aleft = this.visibleX(adiv);
const bleft = this.visibleX(bdiv);
const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top;
const pt1 = [aleft + a.width / 2, atop + a.height / 2];
const pt2 = [bleft + b.width / 2, btop + b.width / 2];
- const pt1vec = [pt1[0] - (aDocBounds.left + aDocBounds.right) / 2, pt1[1] - (aDocBounds.top + aDocBounds.bottom) / 2];
- const pt2vec = [pt2[0] - (bDocBounds.left + bDocBounds.right) / 2, pt2[1] - (bDocBounds.top + bDocBounds.bottom) / 2];
- const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
- const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
+ const pt1vec = [(bDocBounds.left + bDocBounds.right) / 2 - pt1[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt1[1]];
+ const pt2vec = [(aDocBounds.left + aDocBounds.right) / 2 - pt2[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt2[1]];
+ const pt1len = Math.sqrt(pt1vec[0] * pt1vec[0] + pt1vec[1] * pt1vec[1]);
+ const pt2len = Math.sqrt(pt2vec[0] * pt2vec[0] + pt2vec[1] * pt2vec[1]);
const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
- const pt1norm = clipped ? [0, 0] : !pt1Arc ? [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen] :
- Math.abs(acentY - aDocBounds.top) < 0.01 ||
- Math.abs(acentY - aDocBounds.bottom) < 0.01 ? [0, (pt2[1] - pt1[1]) / 2] : [(pt2[0] - pt1[0]) / 2, 0];
- const pt2norm = clipped ? [0, 0] : !pt2Arc ? [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen] :
- Math.abs(bcentY - bDocBounds.top) < 0.01 ||
- Math.abs(bcentY - bDocBounds.bottom) < 0.01 ? [0, (pt1[1] - pt2[1]) / 2] : [(pt1[0] - pt2[0]) / 2, 0];
+ const pt1norm = clipped ? [0, 0] : [(pt1vec[0] / pt1len) * ptlen, (pt1vec[1] / pt1len) * ptlen];
+ const pt2norm = clipped ? [0, 0] : [(pt2vec[0] / pt2len) * ptlen, (pt2vec[1] / pt2len) * ptlen];
const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1;
const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
@@ -229,7 +218,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
}
render() {
- if (!this.renderData) return (null);
+ if (!this.renderData) return null;
const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
LinkManager.currentLink = this.props.LinkDocs[0];
@@ -242,31 +231,37 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
//access stroke color using index of the relationship in the color list (default black)
- const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? "black" : linkColorList[currRelationshipIndex];
+ const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? 'black' : linkColorList[currRelationshipIndex];
// const hexStroke = this.rgbToHex(stroke)
//calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has)
//thickness varies linearly from 3px to 12px for increasing link count
- const strokeWidth = linkSize === -1 ? "3px" : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + "px";
+ const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
if (this.props.LinkDocs[0].displayArrow === undefined) {
this.props.LinkDocs[0].displayArrow = false;
}
- return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <defs>
- <marker id="arrowhead" markerWidth="4" markerHeight="3"
- refX="0" refY="1.5" orient="auto">
- <polygon points="0 0, 3 1.5, 0 3" fill={Colors.DARK_GRAY} />
- </marker>
- </defs>
- <path className="collectionfreeformlinkview-linkLine" style={{ pointerEvents: "all", opacity: this._opacity, stroke: SelectionManager.SelectedSchemaDoc() === this.props.LinkDocs[0] ? Colors.MEDIUM_BLUE : stroke, strokeWidth }}
- onClick={this.onClickLine}
- d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
- markerEnd={this.props.LinkDocs[0].displayArrow ? "url(#arrowhead)" : ""} />
- {textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
- {Field.toString(this.props.LinkDocs[0].description as any as Field)}
- </text>}
- </>);
+ return this.props.LinkDocs[0].opacity === 0 || !a.width || !b.width || (!this.props.LinkDocs[0].linkDisplay && !aActive && !bActive) ? null : (
+ <>
+ <defs>
+ <marker id="arrowhead" markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto">
+ <polygon points="0 0, 3 1.5, 0 3" fill={Colors.DARK_GRAY} />
+ </marker>
+ </defs>
+ <path
+ className="collectionfreeformlinkview-linkLine"
+ style={{ pointerEvents: 'all', opacity: this._opacity, stroke: SelectionManager.SelectedSchemaDoc() === this.props.LinkDocs[0] ? Colors.MEDIUM_BLUE : stroke, strokeWidth }}
+ onClick={this.onClickLine}
+ d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
+ markerEnd={this.props.LinkDocs[0].displayArrow ? 'url(#arrowhead)' : ''}
+ />
+ {textX === undefined ? null : (
+ <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
+ {Field.toString(this.props.LinkDocs[0].description as any as Field)}
+ </text>
+ )}
+ </>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index dacbb3508..8720c9097 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,25 +1,23 @@
-import { computed } from "mobx";
-import { observer } from "mobx-react";
-import { Id } from "../../../../fields/FieldSymbols";
-import { DocumentManager } from "../../../util/DocumentManager";
-import "./CollectionFreeFormLinksView.scss";
-import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
-import React = require("react");
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { Id } from '../../../../fields/FieldSymbols';
+import { DocumentManager } from '../../../util/DocumentManager';
+import './CollectionFreeFormLinksView.scss';
+import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
+import React = require('react');
@observer
-export class CollectionFreeFormLinksView extends React.Component {
+export class CollectionFreeFormLinksView extends React.Component<React.PropsWithChildren<{}>> {
@computed get uniqueConnections() {
- return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c =>
- <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />
- );
+ return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
}
render() {
- return <div className="collectionfreeformlinksview-container">
- <svg className="collectionfreeformlinksview-svgCanvas">
- {this.uniqueConnections}
- </svg>
- {this.props.children}
- </div>;
+ return (
+ <div className="collectionfreeformlinksview-container">
+ <svg className="collectionfreeformlinksview-svgCanvas">{this.uniqueConnections}</svg>
+ {this.props.children}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 9f6938e67..9e8d92d7d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,79 +1,82 @@
-import { computed } from "mobx";
-import { observer } from "mobx-react";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
import * as mobxUtils from 'mobx-utils';
-import CursorField from "../../../../fields/CursorField";
-import { FieldResult } from "../../../../fields/Doc";
-import { List } from "../../../../fields/List";
-import { listSpec } from "../../../../fields/Schema";
-import { Cast } from "../../../../fields/Types";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { CollectionViewProps } from "../CollectionView";
-import "./CollectionFreeFormView.scss";
-import React = require("react");
-import v5 = require("uuid/v5");
+import CursorField from '../../../../fields/CursorField';
+import { Doc, FieldResult } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { Cast } from '../../../../fields/Types';
+import { CollectionViewProps } from '../CollectionView';
+import './CollectionFreeFormView.scss';
+import React = require('react');
+import v5 = require('uuid/v5');
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
-
@computed protected get cursors(): CursorField[] {
const doc = this.props.Document;
let cursors: FieldResult<List<CursorField>>;
- const { id } = CurrentUserUtils;
+ const id = Doc.UserDoc()[Id];
if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) {
return [];
}
const now = mobxUtils.now();
- return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && (now - metadata.timestamp) < 1000);
+ return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && now - metadata.timestamp < 1000);
}
@computed get renderedCursors() {
- return this.cursors.map(({ data: { metadata, position: { x, y } } }) => {
- return (
- <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont"
- style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}
- >
- <canvas className="collectionFreeFormRemoteCursors-canvas"
- ref={(el) => {
- if (el) {
- const ctx = el.getContext('2d');
- if (ctx) {
- ctx.fillStyle = "#" + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + "22";
- ctx.fillRect(0, 0, 20, 20);
+ return this.cursors.map(
+ ({
+ data: {
+ metadata,
+ position: { x, y },
+ },
+ }) => {
+ return (
+ <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
+ <canvas
+ className="collectionFreeFormRemoteCursors-canvas"
+ ref={el => {
+ if (el) {
+ const ctx = el.getContext('2d');
+ if (ctx) {
+ ctx.fillStyle = '#' + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + '22';
+ ctx.fillRect(0, 0, 20, 20);
- ctx.fillStyle = "black";
- ctx.lineWidth = 0.5;
+ ctx.fillStyle = 'black';
+ ctx.lineWidth = 0.5;
- ctx.beginPath();
+ ctx.beginPath();
- ctx.moveTo(10, 0);
- ctx.lineTo(10, 8);
+ ctx.moveTo(10, 0);
+ ctx.lineTo(10, 8);
- ctx.moveTo(10, 20);
- ctx.lineTo(10, 12);
+ ctx.moveTo(10, 20);
+ ctx.lineTo(10, 12);
- ctx.moveTo(0, 10);
- ctx.lineTo(8, 10);
+ ctx.moveTo(0, 10);
+ ctx.lineTo(8, 10);
- ctx.moveTo(20, 10);
- ctx.lineTo(12, 10);
+ ctx.moveTo(20, 10);
+ ctx.lineTo(12, 10);
- ctx.stroke();
+ ctx.stroke();
+ }
}
- }
- }}
- width={20}
- height={20}
- />
- <p className="collectionFreeFormRemoteCursors-symbol">
- {metadata.identifier[0].toUpperCase()}
- </p>
- </div>
- );
- });
+ }}
+ width={20}
+ height={20}
+ />
+ <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
+ </div>
+ );
+ }
+ );
}
render() {
return this.renderedCursors;
}
-} \ 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 e2ea81392..82b377dfa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,84 +1,77 @@
-import { Bezier } from "bezier-js";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { computedFn } from "mobx-utils";
-import { DateField } from "../../../../fields/DateField";
-import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField";
-import { List } from "../../../../fields/List";
-import { ObjectField } from "../../../../fields/ObjectField";
-import { RichTextField } from "../../../../fields/RichTextField";
-import { createSchema, listSpec } 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, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, 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 { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager, dropActionType } from "../../../util/DragManager";
-import { HistoryUtil } from "../../../util/History";
-import { InteractionUtils } from "../../../util/InteractionUtils";
-import { LinkManager } from "../../../util/LinkManager";
-import { SearchUtil } from "../../../util/SearchUtil";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { ColorScheme } from "../../../util/SettingsManager";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH } from "../../../views/global/globalCssVariables.scss";
-import { Timeline } from "../../animationtimeline/Timeline";
-import { ContextMenu } from "../../ContextMenu";
-import { GestureOverlay } from "../../GestureOverlay";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "../../InkingStroke";
-import { LightboxView } from "../../LightboxView";
-import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
-import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { PresBox } from "../../nodes/trails/PresBox";
-import { StyleLayers, StyleProp } from "../../StyleProvider";
-import { CollectionDockingView } from "../CollectionDockingView";
-import { CollectionSubView } from "../CollectionSubView";
-import { CollectionViewType } from "../CollectionView";
-import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
-import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
-import "./CollectionFreeFormView.scss";
-import { MarqueeView } from "./MarqueeView";
-import React = require("react");
-
-export const panZoomSchema = createSchema({
- _panX: "number",
- _panY: "number",
- _currentTimecode: "number",
- _timecodeToShow: "number",
- _currentFrame: "number",
- _useClusters: "boolean",
- _viewTransition: "string",
- _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
- _yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
- _fitToBox: "boolean",
- scrollHeight: "number" // this will be set when the collection is an annotation overlay for a PDF/Webpage
-});
+import { Bezier } from 'bezier-js';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
+import { DateField } from '../../../../fields/DateField';
+import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
+import { List } from '../../../../fields/List';
+import { ObjectField } from '../../../../fields/ObjectField';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { listSpec } from '../../../../fields/Schema';
+import { ScriptField } from '../../../../fields/ScriptField';
+import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { TraceMobx } from '../../../../fields/util';
+import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { CollectionViewType, 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 { ReplayMovements } from '../../../util/ReplayMovements';
+import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { ColorScheme } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss';
+import { Timeline } from '../../animationtimeline/Timeline';
+import { ContextMenu } from '../../ContextMenu';
+import { GestureOverlay } from '../../GestureOverlay';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { LightboxView } from '../../LightboxView';
+import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
+import { FieldViewProps } from '../../nodes/FieldView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { PresBox } from '../../nodes/trails/PresBox';
+import { VideoBox } from '../../nodes/VideoBox';
+import { CreateImage } from '../../nodes/WebBoxRenderer';
+import { StyleProp } from '../../StyleProvider';
+import { CollectionDockingView } from '../CollectionDockingView';
+import { CollectionSubView } from '../CollectionSubView';
+import { TreeViewType } from '../CollectionTreeView';
+import { TabDocView } from '../TabDocView';
+import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
+import './CollectionFreeFormView.scss';
+import { MarqueeView } from './MarqueeView';
+import React = require('react');
+import e = require('connect-flash');
export type collectionFreeformViewProps = {
annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
- childPointerEvents?: boolean;
+ childPointerEvents?: string;
scaleField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
- dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
+ dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
+ dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
// However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
};
@observer
export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
- public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
+ public get displayName() {
+ return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')';
+ } // this makes mobx trace() statements more descriptive
private _lastNudge: any;
private _lastX: number = 0;
@@ -93,65 +86,107 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _disposers: { [name: string]: IReactionDisposer } = {};
private _renderCutoffData = observable.map<string, boolean>();
private _layoutPoolData = observable.map<string, PoolData>();
- private _layoutSizeData = observable.map<string, { width?: number, height?: number }>();
+ private _layoutSizeData = observable.map<string, { width?: number; height?: number }>();
private _cachedPool: Map<string, PoolData> = new Map();
private _lastTap = 0;
private _batch: UndoManager.Batch | undefined = undefined;
- private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
- private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
- private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
+ // private isWritingMode: boolean = true;
+ // private writingModeDocs: Doc[] = [];
+
+ private get isAnnotationOverlay() {
+ return this.props.isAnnotationOverlay;
+ }
+ private get scaleFieldKey() {
+ return this.props.scaleField || '_viewScale';
+ }
+ private get borderWidth() {
+ return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
+ }
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
- @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
+ @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
@observable _hLines: number[] | undefined;
@observable _vLines: number[] | undefined;
@observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.
@observable _pullCoords: number[] = [0, 0];
- @observable _pullDirection: string = "";
+ @observable _pullDirection: string = '';
@observable _showAnimTimeline = false;
- @observable _clusterSets: (Doc[])[] = [];
+ @observable _clusterSets: Doc[][] = [];
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeRef = React.createRef<HTMLDivElement>();
+ @observable _marqueeViewRef = React.createRef<MarqueeView>();
@observable _keyframeEditing = false;
@observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
- @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
- @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); }
- @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); }
+ @computed get views() {
+ return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+ }
@computed get fitToContentVals() {
return {
bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
- scale: !this.childDocs.length ? 1 :
- Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
- this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x))
+ scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
};
}
- @computed get fitToContent() { return (this.props.fitContentsToDoc?.() || this.Document._fitToBox) && !this.isAnnotationOverlay; }
- @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
- @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); }
- @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); }
+ @computed get fitContentsToBox() {
+ return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay;
+ }
+ @computed get contentBounds() {
+ const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
+ return cb
+ ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
+ : this.props.contentBounds?.() ??
+ aggregateBounds(
+ this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!),
+ NumCast(this.layoutDoc._xPadding, 10),
+ NumCast(this.layoutDoc._yPadding, 10)
+ );
+ }
+ @computed get nativeWidth() {
+ return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ }
+ @computed get nativeHeight() {
+ return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ }
@computed get cachedCenteringShiftX(): number {
- const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+ return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
- const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling;// shift so pan position is at center of window for non-overlay collections
+ const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+ return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
- return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
+ return Transform.Identity()
+ .scale(1 / this.zoomScaling())
+ .translate(this.panX(), this.panY());
}
@computed get cachedGetContainerTransform(): Transform {
return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
}
@computed get cachedGetTransform(): Transform {
- return this.getContainerTransform().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
+ return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
}
- @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set;
+ changeKeyFrame = (back = false) => {
+ const currentFrame = Cast(this.Document._currentFrame, 'number', null);
+ if (currentFrame === undefined) {
+ this.Document._currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+ }
+ if (back) {
+ CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+ this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ } else {
+ CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+ this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
+ }
+ };
+ @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
getKeyFrameEditing = () => this._keyframeEditing;
+ onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
@@ -160,30 +195,35 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.layoutDoc._panX = vals.bounds.cx;
this.layoutDoc._panY = vals.bounds.cy;
this.layoutDoc._viewScale = vals.scale;
- }
- freeformData = (force?: boolean) => !this._firstRender && (this.fitToContent || force) ? this.fitToContentVals : undefined;
- reverseNativeScaling = () => this.fitToContent ? true : false;
- panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX);
- panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY);
- zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1));
- contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ };
+ freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined);
+ reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
+ // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
+ // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
+ panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
+ panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
+ zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
+ contentTransform = () =>
+ !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1
+ ? ''
+ : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
getTransform = () => this.cachedGetTransform.copy();
getLocalTransform = () => this.cachedGetLocalTransform.copy();
getContainerTransform = () => this.cachedGetContainerTransform.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
isAnyChildContentActive = () => this.props.isAnyChildContentActive();
addLiveTextBox = (newBox: Doc) => {
- FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newBox);
- }
+ };
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
- }
+ };
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
if (newBox instanceof Doc) {
- if (retVal = (this.props.addDocument?.(newBox) || false)) {
+ if ((retVal = this.props.addDocument?.(newBox) || false)) {
this.bringToFront(newBox);
this.updateCluster(newBox);
}
@@ -192,14 +232,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// bcz: deal with clusters
}
if (retVal) {
- const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
+ const newBoxes = newBox instanceof Doc ? [newBox] : newBox;
for (const newBox of newBoxes) {
if (newBox.activeFrame !== undefined) {
const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field]);
CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field}-indexed`]);
CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field]);
delete newBox.activeFrame;
- CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== "opacity" && (newBox[field] = vals[i]));
+ CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== 'opacity' && (newBox[field] = vals[i]));
}
}
if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) {
@@ -207,26 +247,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
return retVal;
- }
+ };
isCurrent(doc: Doc) {
const dispTime = NumCast(doc._timecodeToShow, -1);
const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
const curTime = NumCast(this.Document._currentTimecode, -1);
- return dispTime === -1 || ((curTime - dispTime) >= -1e-4 && curTime <= endTime);
+ return dispTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime);
}
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
- if (!de.embedKey && !this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
+ if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y);
const z = NumCast(refDoc.z);
const x = (z ? xpo : xp) - docDragData.offset[0];
const y = (z ? ypo : yp) - docDragData.offset[1];
- const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- zsorted.forEach((doc, index) => doc.zIndex = doc.isInkMask ? 5000 : index + 1);
+ const zsorted = this.childLayoutPairs
+ .map(pair => pair.layout)
+ .slice()
+ .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)];
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
@@ -246,8 +289,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
d._lastModified = new DateField();
const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
layoutDoc._width = NumCast(layoutDoc._width, 300);
- layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
- !StrListCast(d._layerTags).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+ layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
+ (d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
@@ -271,13 +314,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@undoBatch
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
- if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) { // dragged document is a child of this collection
- if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) { // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
- const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+ if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
+ // dragged document is a child of this collection
+ if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) {
+ // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
+ const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, 'annotated by:annotation of', ''); // TODODO this is where in text links get passed
}
- e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document
+ e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document
return true;
}
return false;
@@ -289,25 +334,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
return false;
- }
+ };
onExternalDrop = (e: React.DragEvent) => {
return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY));
- }
+ };
pickCluster(probe: number[]) {
- return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
- const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
- if (grouping !== -1) {
- const layoutDoc = Doc.Layout(cd);
- const cx = NumCast(cd.x) - this._clusterDistance;
- const cy = NumCast(cd.y) - this._clusterDistance;
- const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
- const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
- return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
- }
- return cluster;
- }, -1);
+ return this.childLayoutPairs
+ .map(pair => pair.layout)
+ .reduce((cluster, cd) => {
+ const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
+ if (grouping !== -1) {
+ const layoutDoc = Doc.Layout(cd);
+ const cx = NumCast(cd.x) - this._clusterDistance;
+ const cy = NumCast(cd.y) - this._clusterDistance;
+ const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
+ const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
+ return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
+ }
+ return cluster;
+ }, -1);
}
tryDragCluster(e: PointerEvent | TouchEvent, cluster: number) {
@@ -317,10 +364,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
- const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? "alias" : undefined);
+ const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined);
de.moveDocument = this.props.moveDocument;
de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
- DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction });
+ DragManager.StartDocumentDrag(
+ clusterDocs.map(v => v.ContentDiv!),
+ de,
+ ptsParent.clientX,
+ ptsParent.clientY,
+ { hideSource: !de.dropAction }
+ );
return true;
}
}
@@ -343,12 +396,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const docFirst = docs[0];
docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
const preferredInd = NumCast(docFirst.cluster);
- docs.map(doc => doc.cluster = -1);
- docs.map(doc => this._clusterSets.map((set, i) => set.map(member => {
- if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- docFirst.cluster = i;
- }
- })));
+ docs.map(doc => (doc.cluster = -1));
+ docs.map(doc =>
+ this._clusterSets.map((set, i) =>
+ set.map(member => {
+ if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ docFirst.cluster = i;
+ }
+ })
+ )
+ );
if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
docFirst.cluster = preferredInd;
}
@@ -364,7 +421,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
} else if (this._clusterSets.length) {
for (let i = this._clusterSets.length; i <= NumCast(docFirst.cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
- docs.map(doc => this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc));
+ docs.map(doc => this._clusterSets[(doc.cluster = NumCast(docFirst.cluster))].push(doc));
}
childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
}
@@ -378,11 +435,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
const preferredInd = NumCast(doc.cluster);
doc.cluster = -1;
- this._clusterSets.forEach((set, i) => set.forEach(member => {
- if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- doc.cluster = i;
- }
- }));
+ this._clusterSets.forEach((set, i) =>
+ set.forEach(member => {
+ if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ doc.cluster = i;
+ }
+ })
+ );
if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
doc.cluster = preferredInd;
}
@@ -402,7 +461,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
- let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
+ let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
if (property !== StyleProp.BackgroundColor) return styleProp;
const cluster = NumCast(doc?.cluster);
if (this.Document._useClusters) {
@@ -410,16 +469,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
setTimeout(() => doc && this.updateCluster(doc));
} else {
// choose a cluster color from a palette
- const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"];
+ const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
styleProp = colors[cluster % colors.length];
const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
// override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
- set?.filter(s => !StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
- set?.filter(s => StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
+ set?.map(s => (styleProp = StrCast(s.backgroundColor)));
}
} //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray";
return styleProp;
- }
+ };
trySelectCluster = (addToSel: boolean) => {
if (this._hitCluster !== -1) {
@@ -429,71 +487,110 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return true;
}
return false;
- }
+ };
+
+ @action
+ onPenUp = (e: PointerEvent): void => {
+ if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
+ document.removeEventListener('pointerup', this.onPenUp);
+ const currentCol = DocListCast(this.rootDoc.currentInkDoc);
+ const rootDocList = DocListCast(this.rootDoc.data);
+ currentCol.push(rootDocList[rootDocList.length - 1]);
+
+ this._batch?.end();
+ }
+ };
@action
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.pageX;
this._downY = this._lastY = e.pageY;
if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
- if (!e.nativeEvent.cancelBubble &&
+ if (
!this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
- !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- switch (CurrentUserUtils.SelectedTool) {
+ !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)
+ ) {
+ switch (Doc.ActiveTool) {
case InkTool.Highlighter:
- case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+ break;
+ // TODO: nda - this where we want to create the new "writingDoc" collection that we add strokes to
+ case InkTool.Write:
+ break;
+ case InkTool.Pen:
+ break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
case InkTool.Eraser:
- document.addEventListener("pointermove", this.onEraserMove);
- document.addEventListener("pointerup", this.onEraserUp);
- this._batch = UndoManager.StartBatch("collectionErase");
+ document.addEventListener('pointermove', this.onEraserMove);
+ document.addEventListener('pointerup', this.onEraserUp);
+ this._batch = UndoManager.StartBatch('collectionErase');
e.stopPropagation();
e.preventDefault();
break;
case InkTool.None:
- this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
+ if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
+ this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
+ document.addEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointerup', this.onPointerUp);
+ }
break;
}
}
}
- }
+ };
@action
handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble) {
- // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt = me.changedTouches[0];
- if (pt) {
- this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY));
- if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- if (CurrentUserUtils.SelectedTool === InkTool.None) {
- this._lastX = pt.pageX;
- this._lastY = pt.pageY;
- e.preventDefault();
- e.stopPropagation();
- }
- else {
- e.preventDefault();
- }
+ // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
+ const pt = me.changedTouches[0];
+ if (pt) {
+ this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY));
+ if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ if (Doc.ActiveTool === InkTool.None) {
+ this._lastX = pt.pageX;
+ this._lastY = pt.pageY;
+ e.preventDefault();
+ e.stopPropagation();
+ } else {
+ e.preventDefault();
}
}
}
- }
+ };
+ public unprocessedDocs: Doc[] = [];
+ public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>();
@undoBatch
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
switch (ge.gesture) {
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
- { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
+ const inkDoc = Docs.Create.InkDocument(
+ ActiveInkColor(),
+ Doc.ActiveTool,
+ ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
+ ActiveInkBezierApprox(),
+ ActiveFillColor(),
+ ActiveArrowStart(),
+ ActiveArrowEnd(),
+ ActiveDash(),
+ points,
+ {
+ title: 'ink stroke',
+ x: B.x - ActiveInkWidth() / 2,
+ y: B.y - ActiveInkWidth() / 2,
+ _width: B.width + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
+ _height: B.height + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
+ }
+ );
+ if (Doc.ActiveTool === InkTool.Write) {
+ this.unprocessedDocs.push(inkDoc);
+ CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
+ }
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -515,7 +612,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
return pass;
});
- this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
+ this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
sel.forEach(d => this.props.removeDocument?.(d));
e.stopPropagation();
break;
@@ -527,17 +624,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case GestureUtils.Gestures.EndBracket:
if (this._inkToTextStartX && this._inkToTextStartY) {
const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
- const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color);
- const sets = setDocs.map((sd) => {
+ const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color);
+ const sets = setDocs.map(sd => {
return Cast(sd.text, RichTextField)?.Text as string;
});
if (sets.length && sets[0]) {
this._wordPalette.clear();
const colors = setDocs.map(sd => FieldValue(sd.color) as string);
- sets.forEach((st: string, i: number) => st.split(",").forEach(word => this._wordPalette.set(word, colors[i])));
+ sets.forEach((st: string, i: number) => st.split(',').forEach(word => this._wordPalette.set(word, colors[i])));
}
const inks = this.getActiveDocuments().filter(doc => {
- if (doc.type === "ink") {
+ if (doc.type === 'ink') {
const l = NumCast(doc.x);
const r = l + doc[WidthSym]();
const t = NumCast(doc.y);
@@ -553,15 +650,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const d = Cast(i.data, InkField);
const x = NumCast(i.x);
const y = NumCast(i.y);
- const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]);
- const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]);
+ const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
+ const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
if (d) {
strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
}
});
- CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
- const wordResults = results.filter((r: any) => r.category === "inkWord");
+ CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {
+ const wordResults = results.filter((r: any) => r.category === 'inkWord');
for (const word of wordResults) {
const indices: number[] = word.strokeIds;
indices.forEach(i => {
@@ -573,8 +670,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
inks[i].alternativeColors = new List<string>(uniqueColors);
if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
- }
- else if (word.alternates) {
+ } else if (word.alternates) {
for (const alt of word.alternates) {
if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
@@ -595,32 +691,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
}
}
- }
+ };
@action
onEraserUp = (e: PointerEvent): void => {
if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
- document.removeEventListener("pointermove", this.onEraserMove);
- document.removeEventListener("pointerup", this.onEraserUp);
+ document.removeEventListener('pointermove', this.onEraserMove);
+ document.removeEventListener('pointerup', this.onEraserUp);
this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
this._deleteList = [];
this._batch?.end();
}
- }
+ };
@action
onPointerUp = (e: PointerEvent): void => {
if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
this.removeMoveListeners();
this.removeEndListeners();
}
- }
+ };
onClick = (e: React.MouseEvent) => {
- if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
+ if (this.onBrowseClickHandler()) {
+ if (this.props.DocumentView?.()) {
+ this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY });
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ } else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) {
if (e.shiftKey) {
- if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click
+ if (Date.now() - this._lastTap < 300) {
+ // reset zoom of freeform view to 1-to-1 on a shift + double click
this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
}
e.stopPropagation();
@@ -628,15 +731,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
this._lastTap = Date.now();
}
- }
+ };
@action
- pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
+ pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => {
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
- }
+ };
/**
* Erases strokes by intersecting them with an invisible "eraser stroke".
@@ -650,14 +753,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.getEraserIntersections({ X: this._lastX, Y: this._lastY }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
this._deleteList.push(intersect.inkView);
- SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || "1");
- SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || "black");
+ SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1');
+ SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
- !e.shiftKey && this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke,
- segment.reduce((data, curve) => [...data, ...curve.points
- .map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })
- ], [] as PointData[])));
+ !e.shiftKey &&
+ this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
+ GestureOverlay.Instance.dispatchGesture(
+ GestureUtils.Gestures.Stroke,
+ segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
+ )
+ );
// Lower ink opacity to give the user a visual indicator of deletion.
intersect.inkView.layoutDoc.opacity = 0.5;
}
@@ -667,54 +772,64 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
- }
+ };
@action
onPointerMove = (e: PointerEvent): void => {
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
+ Doc.ActiveTool = InkTool.None;
if (this.props.isContentActive(true)) e.stopPropagation();
} else if (!e.cancelBubble) {
if (this.tryDragCluster(e, this._hitCluster)) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
- else this.pan(e);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
+ } else this.pan(e);
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
- }
+ };
/**
* Determines if the Eraser tool has intersected with an ink stroke in the current freeform collection.
* @returns an array of tuples containing the intersected ink DocumentView and the t-value where it was intersected
*/
- getEraserIntersections = (lastPoint: { X: number, Y: number }, currPoint: { X: number, Y: number }) => {
+ getEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => {
const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) };
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
return this.childDocs
.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
.map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
- .filter(({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap
- eraserMin.X <= inkViewBounds.right && eraserMin.Y <= inkViewBounds.bottom &&
- eraserMax.X >= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top)
+ .filter(
+ ({ inkViewBounds }) =>
+ inkViewBounds && // bounding box of eraser segment and ink stroke overlap
+ eraserMin.X <= inkViewBounds.right &&
+ eraserMin.Y <= inkViewBounds.bottom &&
+ eraserMax.X >= inkViewBounds.left &&
+ eraserMax.Y >= inkViewBounds.top
+ )
.reduce((intersections, { inkStroke, inkView }) => {
const { inkData } = inkStroke.inkScaledData();
// Convert from screen space to ink space for the intersection.
const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
for (var i = 0; i < inkData.length - 3; i += 4) {
- const intersects = Array.from(new Set(InkField.Segment(inkData, i).intersects({ // compute all unique intersections
- p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
- p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }
- }) as (number | string)[])); // convert to more manageable union array type
+ const intersects = Array.from(
+ new Set(
+ InkField.Segment(inkData, i).intersects({
+ // compute all unique intersections
+ p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
+ p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
+ }) as (number | string)[]
+ )
+ ); // convert to more manageable union array type
// return tuples of the inkingStroke intersected, and the t value of the intersection
- intersections.push(...intersects.map(t => ({ inkView, t: (+t) + Math.floor(i / 4) })));// convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
+ intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
}
return intersections;
- }, [] as { t: number, inkView: DocumentView }[]);
- }
+ }, [] as { t: number; inkView: DocumentView }[]);
+ };
/**
* Performs segmentation of the ink stroke - creates "segments" or subsections of the current ink stroke at points in which the
@@ -751,11 +866,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
segment.push(inkSegment);
}
}
- if (excludeT < startSegmentT || excludeT > (inkData.length / 4)) {
+ if (excludeT < startSegmentT || excludeT > inkData.length / 4) {
segment.length && segments.push(segment);
}
return segments;
- }
+ };
/**
* Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all
@@ -783,33 +898,34 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
curve.intersects(otherCurve).forEach((val: string | number, i: number) => {
// Converting the Bezier.js Split type to a t-value number.
- const t = +val.toString().split("/")[0];
- if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
+ const t = +val.toString().split('/')[0];
+ if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
});
}
});
return tVals;
- }
+ };
handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
if (!e.cancelBubble) {
const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
if (myTouches[0]) {
- if (CurrentUserUtils.SelectedTool === InkTool.None) {
+ if (Doc.ActiveTool === InkTool.None) {
if (this.tryDragCluster(e, this._hitCluster)) {
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
return;
}
+ // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
this.pan(myTouches[0]);
}
}
// e.stopPropagation();
e.preventDefault();
}
- }
+ };
handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
// pinch zooming
@@ -832,7 +948,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
// calculate the raw delta value
- const rawDelta = (dir * (d1 + d2));
+ const rawDelta = dir * (d1 + d2);
// this floors and ceils the delta value to prevent jitteriness
const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
@@ -847,7 +963,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
// const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
- if (!this._pullDirection) { // if we are not bezel movement
+ if (!this._pullDirection) {
+ // if we are not bezel movement
this.pan({ clientX: centerX, clientY: centerY });
} else {
this._pullCoords = [centerX, centerY];
@@ -861,11 +978,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// e.stopPropagation();
e.preventDefault();
}
- }
+ };
@action
handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble && this.props.isContentActive(true)) {
+ if (this.props.isContentActive(true)) {
// const pt1: React.Touch | null = e.targetTouches.item(0);
// const pt2: React.Touch | null = e.targetTouches.item(1);
// // if (!pt1 || !pt2) return;
@@ -879,21 +996,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._lastY = centerY;
const screenBox = this._mainCont?.getBoundingClientRect();
-
// determine if we are using a bezel movement
if (screenBox) {
- if ((screenBox.right - centerX) < 100) {
+ if (screenBox.right - centerX < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "right";
+ this._pullDirection = 'right';
} else if (centerX - screenBox.left < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "left";
+ this._pullDirection = 'left';
} else if (screenBox.bottom - centerY < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "bottom";
+ this._pullDirection = 'bottom';
} else if (centerY - screenBox.top < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "top";
+ this._pullDirection = 'top';
}
}
@@ -904,35 +1020,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
}
}
- }
+ };
cleanUpInteractions = () => {
switch (this._pullDirection) {
- case "left": case "right": case "top": case "bottom":
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection);
+ case 'left':
+ case 'right':
+ case 'top':
+ case 'bottom':
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: 'New Collection' }), this._pullDirection);
}
- this._pullDirection = "";
+ this._pullDirection = '';
this._pullCoords = [0, 0];
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
this.removeMoveListeners();
this.removeEndListeners();
- }
+ };
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
if (this.Document._isGroup) return;
- let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05;
+ let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
const [x, y] = this.getTransform().transformPoint(pointX, pointY);
const invTransform = this.getLocalTransform().inverse();
if (deltaScale * invTransform.Scale > 20) {
deltaScale = 20 / invTransform.Scale;
}
- if (deltaScale * invTransform.Scale < 1 && this.isAnnotationOverlay) {
- deltaScale = 1 / invTransform.Scale;
+ if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) {
+ deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale;
}
const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
@@ -941,51 +1060,63 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
}
- }
+ };
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.layoutDoc._lockedTransform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === "outline") return;
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
+ if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
+ if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
+ // things that can scroll vertically should do that instead of zooming
e.stopPropagation();
- }
- else if (this.props.isContentActive(true) && !this.Document._isGroup) {
+ } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
e.stopPropagation();
e.preventDefault();
!this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
}
- }
+ };
@action
setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
+ // this is the easiest way to do this -> will talk with Bob about using mobx to do this to remove this line of code.
+ if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction();
+
if (!this.isAnnotationOverlay && clamp) {
// this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
- const measuredDocs = docs.map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ""), size: this.childSizeProviderUnmemoized(doc, "") }))
- .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! }));
+ const measuredDocs = docs
+ .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') }))
+ .filter(({ pos, size }) => pos && size)
+ .map(({ pos, size }) => ({ pos: pos!, size: size! }));
if (measuredDocs.length) {
- const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content
- ({
- xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
- yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }
- })
- , {
+ const ranges = measuredDocs.reduce(
+ (
+ { xrange, yrange },
+ { pos, size } // computes range of content
+ ) => ({
+ xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
+ yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
+ }),
+ {
xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
- });
+ yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ }
+ );
const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
- if (ranges.xrange.min >= (panX + panelDim[0] / 2)) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
- else if (ranges.xrange.max <= (panX - panelDim[0] / 2)) panX = ranges.xrange.min - panelDim[0] / 2;
- if (ranges.yrange.min >= (panY + panelDim[1] / 2)) panY = ranges.yrange.max + panelDim[1] / 2;
- else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
+ if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
+ else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2;
+ if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2;
+ else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2;
}
}
- if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || CurrentUserUtils.OverlayDocs.includes(this.Document)) {
+ if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
this._viewTransition = panTime;
const scale = this.getLocalTransform().inverse().Scale;
- const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
- const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
+ const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
+ const minPanX = NumCast(this.rootDoc._panXMin, 0);
+ const minPanY = NumCast(this.rootDoc._panYMin, 0);
+ const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX));
+ const newPanY = Math.min(this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight), Math.max(minPanY, panY));
!this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
!this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
}
@@ -993,26 +1124,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
- if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ||
- this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out...
- this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
- NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), nudgeTime, true);
+ if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
+ // bcz: this isn't ideal, but want to try it out...
+ this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true);
this._lastNudge && clearTimeout(this._lastNudge);
- this._lastNudge = setTimeout(action(() => this._viewTransition = 0), nudgeTime);
+ this._lastNudge = setTimeout(
+ action(() => (this._viewTransition = 0)),
+ nudgeTime
+ );
return true;
}
return false;
- }
+ };
@action
bringToFront = (doc: Doc, sendToBack?: boolean) => {
- if (sendToBack || StrListCast(doc._layerTags).includes(StyleLayers.Background)) {
+ if (sendToBack) {
doc.zIndex = 0;
} else if (doc.isInkMask) {
doc.zIndex = 5000;
} else {
- const docs = this.childLayoutPairs.map(pair => pair.layout);
- docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
+ docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1;
if (zlast - docs.length > 100) {
for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
@@ -1020,12 +1153,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
doc.zIndex = zlast + 1;
}
- }
+ };
@action
zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
if (this.Document._isGroup) return;
- setTimeout(action(() => this._viewTransition = 0), this._viewTransition = transitionTime); // set transition to be smooth, then reset
+ setTimeout(
+ action(() => (this._viewTransition = 0)),
+ (this._viewTransition = transitionTime)
+ ); // set transition to be smooth, then reset
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
@@ -1040,7 +1176,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// 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) {
+ if (state.type === 'doc' && this.Document._panX !== undefined && this.Document._panY !== undefined) {
const init = state.initializers![this.Document[Id]];
if (!init) {
state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) };
@@ -1051,29 +1187,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
HistoryUtil.pushState(state);
}
}
- if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
- SelectionManager.DeselectAll();
- }
+ // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
+ // SelectionManager.DeselectAll();
+ // }
if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined) {
this.props.focus(doc, options);
} else {
const xfToCollection = options?.docTransform ?? Transform.Identity();
- const layoutdoc = Doc.Layout(doc);
- const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey] };
+ const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined };
const newState = HistoryUtil.getState();
- const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
- const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
- if (!cantTransform) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
+ const cantTransform = /*this.props.isAnnotationOverlay ||*/ (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc;
+ const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || 0.75 : undefined);
+ if (!cantTransform) {
+ // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
HistoryUtil.pushState(newState);
}
// focus on the document in the collection
- const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined);
+ const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
const focusSpeed = options?.instant ? 0 : didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
// glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
if (didMove) {
- this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
scale && (this.Document[this.scaleFieldKey] = scale);
+ this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
}
const startTime = Date.now();
@@ -1083,225 +1219,277 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing;
if (resetView) {
const restoreState = (!LightboxView.LightboxDoc || LightboxView.LightboxDoc === this.props.Document) && savedState;
- if (typeof restoreState !== "boolean") {
+ if (typeof restoreState !== 'boolean') {
this.Document._panX = restoreState.panX;
this.Document._panY = restoreState.panY;
this.Document[this.scaleFieldKey] = restoreState.scale;
}
- runInAction(() => this._viewTransition = 0);
+ runInAction(() => (this._viewTransition = 0));
}
return resetView;
};
- const xf = !cantTransform ? Transform.Identity() :
- this.props.isAnnotationOverlay ?
- new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc))
- :
- new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX),
- NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
+ const xf = !cantTransform
+ ? Transform.Identity()
+ : this.props.isAnnotationOverlay
+ ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc))
+ : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
this.props.focus(cantTransform ? doc : this.rootDoc, {
...options,
docTransform: xf,
- afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res =>
- setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime))))
+ afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))),
});
}
- }
+ };
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
- const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
- const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
+ const layoutdoc = Doc.Layout(doc);
const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
- const pt2 = xf.transformPoint(NumCast(doc.x) + doc[WidthSym](), NumCast(doc.y) + doc[HeightSym]());
- const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] };
- const cx = NumCast(this.layoutDoc._panX);
- const cy = NumCast(this.layoutDoc._panY);
- const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
+ const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
+ const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
if (scale) {
- const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
+ const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
+ const newScale = Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
return {
- panX: (bounds.left + bounds.right) / 2,
- panY: (bounds.top + bounds.bot) / 2,
- scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])))
+ panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
+ panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
+ scale: newScale,
};
}
- if ((screen.right - screen.left) < (bounds.right - bounds.left) ||
- (screen.bot - screen.top) < (bounds.bot - bounds.top)) {
+ const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
+ const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
+ const cx = NumCast(this.layoutDoc._panX);
+ const cy = NumCast(this.layoutDoc._panY);
+ const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
+ if (screen.right - screen.left < bounds.right - bounds.left || screen.bot - screen.top < bounds.bot - bounds.top) {
return {
panX: (bounds.left + bounds.right) / 2,
panY: (bounds.top + bounds.bot) / 2,
- scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1
+ scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
};
}
return {
panX: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
panY: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot),
};
- }
+ };
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
- getChildDocView(entry: PoolData, renderIndex: number) {
+ @undoBatch
+ @action
+ onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ const docView = fieldProps.DocumentView?.();
+ if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ['Tab', 'Enter'].includes(e.key)) {
+ e.stopPropagation?.();
+ const below = !e.altKey && e.key !== 'Tab';
+ const layoutKey = StrCast(docView.LayoutFieldKey);
+ const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+ const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
+ newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+ if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
+ else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
+ if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) {
+ newDoc[layoutKey] = docView.rootDoc[layoutKey];
+ }
+ Doc.GetProto(newDoc).text = undefined;
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ return this.addDocument?.(newDoc);
+ }
+ };
+ pointerEvents = () => {
+ const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+ return pointerEvents;
+ };
+ getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
- const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
- return <CollectionFreeFormDocumentView key={childLayout[Id] + (entry.replica || "")}
- DataDoc={childData}
- Document={childLayout}
- renderDepth={this.props.renderDepth + 1}
- replica={entry.replica}
- renderIndex={renderIndex}
- renderCutoffProvider={this.renderCutoffProvider}
- ContainingCollectionView={this.props.CollectionView}
- ContainingCollectionDoc={this.props.Document}
- CollectionFreeFormView={this}
- LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
- LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
- rootSelected={childData ? this.rootSelected : returnFalse}
- onClick={this.onChildClickHandler}
- onDoubleClick={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
- PanelWidth={childLayout[WidthSym]}
- PanelHeight={childLayout[HeightSym]}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- isContentActive={emptyFunction}
- isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
- focus={this.focusDocument}
- addDocTab={this.addDocTab}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- moveDocument={this.props.moveDocument}
- pinToPres={this.props.pinToPres}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- docViewPath={this.props.docViewPath}
- styleProvider={this.getClusterColor}
- layerProvider={this.props.layerProvider}
- dataProvider={this.childDataProvider}
- sizeProvider={this.childSizeProvider}
- freezeDimensions={this.props.childFreezeDimensions}
- dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
- bringToFront={this.bringToFront}
- showTitle={this.props.childShowTitle}
- dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
- pointerEvents={this.props.isContentActive() === false ? "none" : this.backgroundActive || this.props.childPointerEvents ? "all" :
- (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents}
- jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
- //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
- />;
+ return (
+ <CollectionFreeFormDocumentView
+ key={childLayout[Id] + (entry.replica || '')}
+ DataDoc={childData}
+ Document={childLayout}
+ renderDepth={this.props.renderDepth + 1}
+ replica={entry.replica}
+ suppressSetHeight={this.layoutEngine ? true : false}
+ renderCutoffProvider={this.renderCutoffProvider}
+ ContainingCollectionView={this.props.CollectionView}
+ ContainingCollectionDoc={this.props.Document}
+ CollectionFreeFormView={this}
+ LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
+ LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
+ rootSelected={childData ? this.rootSelected : returnFalse}
+ onClick={this.onChildClickHandler}
+ onKey={this.onKeyDown}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ onBrowseClick={this.onBrowseClickHandler}
+ ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
+ PanelWidth={childLayout[WidthSym]}
+ PanelHeight={childLayout[HeightSym]}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isContentActive={emptyFunction}
+ focus={this.focusDocument}
+ addDocTab={this.addDocTab}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ moveDocument={this.props.moveDocument}
+ pinToPres={this.props.pinToPres}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ docViewPath={this.props.docViewPath}
+ styleProvider={this.getClusterColor}
+ dataProvider={this.childDataProvider}
+ sizeProvider={this.childSizeProvider}
+ dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+ bringToFront={this.bringToFront}
+ showTitle={this.props.childShowTitle}
+ dontScaleFilter={this.props.dontScaleFilter}
+ dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
+ pointerEvents={this.pointerEvents}
+ jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
+ //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+ />
+ );
}
addDocTab = action((doc: Doc, where: string) => {
- if (where === "inParent") {
- ((doc instanceof Doc) ? [doc] : doc).forEach(doc => {
+ if (where === 'inParent') {
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => {
const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = pt[0];
doc.y = pt[1];
});
return this.props.addDocument?.(doc) || false;
}
- if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]);
return true;
}
return this.props.addDocTab(doc, where);
});
- getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc }): PoolData {
+ getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
const layoutDoc = Doc.Layout(params.pair.layout);
const { z, color, zIndex } = params.pair.layout;
- const { x, y, opacity } = this.Document._currentFrame === undefined ?
- { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } :
- CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
+ const { x, y, opacity } =
+ this.Document._currentFrame === undefined
+ ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) }
+ : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
return {
- x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
- transition: StrCast(layoutDoc.dataTransition), opacity: this._keyframeEditing ? 1 : Cast(opacity, "number", null),
- width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
+ x: NumCast(x),
+ y: NumCast(y),
+ z: Cast(z, 'number'),
+ color: StrCast(color),
+ zIndex: Cast(zIndex, 'number'),
+ transition: StrCast(layoutDoc.dataTransition),
+ opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number', null),
+ width: Cast(layoutDoc._width, 'number'),
+ height: Cast(layoutDoc._height, 'number'),
+ pair: params.pair,
+ replica: '',
};
}
onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
(this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
e.stopPropagation();
- }
+ };
viewDefsToJSX = (views: ViewDefBounds[]) => {
return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
- }
+ };
viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
const { x, y, z } = viewDef;
const color = StrCast(viewDef.color);
- const width = Cast(viewDef.width, "number");
- const height = Cast(viewDef.height, "number");
+ const width = Cast(viewDef.width, 'number');
+ const height = Cast(viewDef.height, 'number');
const transform = `translate(${x}px, ${y}px)`;
- if (viewDef.type === "text") {
- const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
- const fontSize = Cast(viewDef.fontSize, "string");
- return [text, x, y].some(val => val === undefined) ? undefined :
- {
- ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
- {text}
- </div>,
- bounds: viewDef
- };
- } else if (viewDef.type === "div") {
- return [x, y].some(val => val === undefined) ? undefined :
- {
- ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z + viewDef.payload} onClick={e => this.onViewDefDivClick(e, viewDef)}
- style={{ width, height, backgroundColor: color, transform }} />,
- bounds: viewDef
- };
+ if (viewDef.type === 'text') {
+ const text = Cast(viewDef.text, 'string'); // don't use NumCast, StrCast, etc since we want to test for undefined below
+ const fontSize = Cast(viewDef.fontSize, 'string');
+ return [text, x, y].some(val => val === undefined)
+ ? undefined
+ : {
+ ele: (
+ <div className="collectionFreeform-customText" key={(text || '') + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
+ {text}
+ </div>
+ ),
+ bounds: viewDef,
+ };
+ } else if (viewDef.type === 'div') {
+ return [x, y].some(val => val === undefined)
+ ? undefined
+ : {
+ ele: (
+ <div
+ className="collectionFreeform-customDiv"
+ title={viewDef.payload?.join(' ')}
+ key={'div' + x + y + z + viewDef.payload}
+ onClick={e => this.onViewDefDivClick(e, viewDef)}
+ style={{ width, height, backgroundColor: color, transform }}
+ />
+ ),
+ bounds: viewDef,
+ };
}
}
-
- renderCutoffProvider = computedFn(function renderCutoffProvider(this: any, doc: Doc) {
- return !this._renderCutoffData.get(doc[Id] + "");
- }.bind(this));
-
+ renderCutoffProvider = computedFn(
+ function renderCutoffProvider(this: any, doc: Doc) {
+ return !this._renderCutoffData.get(doc[Id] + '');
+ }.bind(this)
+ );
childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
- return this._layoutPoolData.get(doc[Id] + (replica || ""));
- }
- childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
- return this._layoutPoolData.get(doc[Id] + (replica || ""));
- }.bind(this));
+ return this._layoutPoolData.get(doc[Id] + (replica || ''));
+ };
+ childDataProvider = computedFn(
+ function childDataProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutPoolData.get(doc[Id] + (replica || ''));
+ }.bind(this)
+ );
childSizeProviderUnmemoized = (doc: Doc, replica: string) => {
- return this._layoutSizeData.get(doc[Id] + (replica || ""));
- }
- childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
- return this._layoutSizeData.get(doc[Id] + (replica || ""));
- }.bind(this));
-
- doEngineLayout(poolData: Map<string, PoolData>,
- engine: (
- poolData: Map<string, PoolData>,
- pivotDoc: Doc,
- childPairs: { layout: Doc, data?: Doc }[],
- panelDim: number[],
- viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[]),
- engineProps: any) => ViewDefResult[]
+ return this._layoutSizeData.get(doc[Id] + (replica || ''));
+ };
+ childSizeProvider = computedFn(
+ function childSizeProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutSizeData.get(doc[Id] + (replica || ''));
+ }.bind(this)
+ );
+
+ doEngineLayout(
+ poolData: Map<string, PoolData>,
+ engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
) {
return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps);
}
doFreeformLayout(poolData: Map<string, PoolData>) {
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) =>
- poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
return [] as ViewDefResult[];
}
+ @computed get layoutEngine() {
+ return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
+ }
@computed get doInternalLayoutComputation() {
TraceMobx();
const newPool = new Map<string, PoolData>();
- switch (this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine)) {
- case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
- case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
- case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
- case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
+ switch (this.layoutEngine) {
+ case 'pass':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
+ case 'timeline':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
+ case 'pivot':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
+ case 'starburst':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
}
return { newPool, computedElementData: this.doFreeformLayout(newPool) };
}
@@ -1325,15 +1513,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements = computedElementData.slice();
- Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) =>
- elements.push({
- ele: this.getChildDocView(entry[1], i),
- bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
- }));
-
- if (this.props.isAnnotationOverlay) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
- if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, NumCast(this.props.Document[this.scaleFieldKey], 1));
- else this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
+ Array.from(newPool.entries())
+ .filter(entry => this.isCurrent(entry[1].pair.layout))
+ .forEach((entry, i) =>
+ elements.push({
+ ele: this.getChildDocView(entry[1]),
+ bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
+ })
+ );
+
+ if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
+ // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
+ if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
+ else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
}
this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
@@ -1354,71 +1546,157 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
return 0;
- }
+ };
getAnchor = () => {
if (this.props.Document.annotationOn) {
return this.rootDoc;
}
- const anchor = Docs.Create.TextanchorDocument({ title: "ViewSpec - " + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc });
+ const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc });
const proto = Doc.GetProto(anchor);
- proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType;
+ proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType;
proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
- if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>([anchor]);
+ this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]);
}
return anchor;
- }
+ };
@action
componentDidMount() {
super.componentDidMount?.();
this.props.setContentView?.(this);
- setTimeout(action(() => {
- this._firstRender = false;
- this._disposers.layoutComputation = reaction(() => this.doLayoutComputation,
- (elements) => this._layoutElements = elements || [],
- { fireImmediately: true, name: "doLayout" });
-
- this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
-
- this._disposers.groupBounds = reaction(() => {
- if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
- const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
- return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
+ setTimeout(
+ action(() => {
+ this._firstRender = false;
+ this._disposers.layoutComputation = reaction(
+ () => this.doLayoutComputation,
+ elements => (this._layoutElements = elements || []),
+ { fireImmediately: true, name: 'doLayout' }
+ );
+
+ this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+
+ this._disposers.groupBounds = reaction(
+ () => {
+ if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+ const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
+ return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
+ }
+ return undefined;
+ },
+ cbounds => {
+ if (cbounds) {
+ const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
+ const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+ const pbounds = {
+ x: (cbounds.x - p[0]) * this.zoomScaling() + c[0],
+ y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
+ r: (cbounds.r - p[0]) * this.zoomScaling() + c[0],
+ b: (cbounds.b - p[1]) * this.zoomScaling() + c[1],
+ };
+ this.layoutDoc._width = pbounds.r - pbounds.x;
+ this.layoutDoc._height = pbounds.b - pbounds.y;
+ this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
+ this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+ this.layoutDoc.x = pbounds.x;
+ this.layoutDoc.y = pbounds.y;
+ }
+ },
+ { fireImmediately: true }
+ );
+ })
+ );
+ }
+
+ static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
+ if (oldDiv.childNodes && newDiv) {
+ for (let i = 0; i < oldDiv.childNodes.length; i++) {
+ this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
+ }
+ }
+ if (oldDiv instanceof HTMLCanvasElement) {
+ if (oldDiv.className === 'collectionFreeFormView-grid') {
+ const newCan = newDiv as HTMLCanvasElement;
+ const parEle = newCan.parentElement as HTMLElement;
+ parEle.removeChild(newCan);
+ parEle.appendChild(document.createElement('div'));
+ } else {
+ const canvas = oldDiv;
+ const img = document.createElement('img'); // create a Image Element
+ img.src = canvas.toDataURL(); //image source
+ img.style.width = canvas.style.width;
+ img.style.height = canvas.style.height;
+ const newCan = newDiv as HTMLCanvasElement;
+ if (newCan) {
+ const parEle = newCan.parentElement as HTMLElement;
+ parEle.removeChild(newCan);
+ parEle.appendChild(img);
}
- return undefined;
- },
- (cbounds) => {
- if (cbounds) {
- const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
- const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
- const pbounds = {
- x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
- r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1]
- };
- this.layoutDoc._width = (pbounds.r - pbounds.x);
- this.layoutDoc._height = (pbounds.b - pbounds.y);
- this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
- this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
- this.layoutDoc.x = pbounds.x;
- this.layoutDoc.y = pbounds.y;
- }
- }, { fireImmediately: true });
- }));
+ }
+ }
+ }
+
+ updateIcon = () =>
+ CollectionFreeFormView.UpdateIcon(
+ this.layoutDoc[Id] + '-icon' + new Date().getTime(),
+ this.props.docViewPath().lastElement().ContentDiv!,
+ this.layoutDoc[WidthSym](),
+ this.layoutDoc[HeightSym](),
+ this.props.PanelWidth(),
+ this.props.PanelHeight(),
+ 0,
+ 1,
+ false,
+ '',
+ (iconFile, nativeWidth, nativeHeight) => {
+ this.dataDoc.icon = new ImageField(iconFile);
+ this.dataDoc['icon-nativeWidth'] = nativeWidth;
+ this.dataDoc['icon-nativeHeight'] = nativeHeight;
+ }
+ );
+
+ public static UpdateIcon(
+ filename: string,
+ docViewContent: HTMLElement,
+ width: number,
+ height: number,
+ panelWidth: number,
+ panelHeight: number,
+ scrollTop: number,
+ realNativeHeight: number,
+ noSuffix: boolean,
+ replaceRootFilename: string | undefined,
+ cb: (iconFile: string, nativeWidth: number, nativeHeight: number) => any
+ ) {
+ const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
+ newDiv.style.width = width.toString();
+ newDiv.style.height = height.toString();
+ this.replaceCanvases(docViewContent, newDiv);
+ const htmlString = new XMLSerializer().serializeToString(newDiv);
+ const nativeWidth = width;
+ const nativeHeight = height;
+ return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
+ .then(async (data_url: any) => {
+ const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
+ cb(returnedFilename as string, nativeWidth, nativeHeight);
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
}
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
+ this._marqueeRef.current?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
}
@action
onCursorMove = (e: React.PointerEvent) => {
// super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
- }
+ };
@action
onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
@@ -1438,7 +1716,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
e.stopPropagation();
- }
+ };
@undoBatch
promoteCollection = () => {
@@ -1448,9 +1726,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
doc.x = scr?.[0];
doc.y = scr?.[1];
});
- this.props.addDocTab(childDocs as any as Doc, "inParent");
+ this.props.addDocTab(childDocs as any as Doc, 'inParent');
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
- }
+ };
@undoBatch
layoutDocsInGrid = () => {
@@ -1459,82 +1737,103 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
const dim = Math.ceil(Math.sqrt(docs.length));
docs.forEach((doc, i) => {
- doc.x = NumCast(this.Document._panX) + (i % dim) * width - width * dim / 2;
- doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - height * dim / 2;
+ doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2;
+ doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2;
});
- }
+ };
@undoBatch
- toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight)
+ toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight);
onContextMenu = (e: React.MouseEvent) => {
if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return;
- const appearance = ContextMenu.Instance.findByDescription("Appearance...");
- const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
- appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
- !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
- appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
- this.props.ContainingCollectionView &&
- appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" });
- !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
- !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
-
- const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
- const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
-
- !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null;
- !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null;
- !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
-
- const options = ContextMenu.Instance.findByDescription("Options...");
- const optionItems = options && "subitems" in options ? options.subitems : [];
- !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
- optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" });
- this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
- if (!Doc.UserDoc().noviceMode) {
- optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
- optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
+ const appearance = ContextMenu.Instance.findByDescription('Appearance...');
+ const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
+ appearanceItems.push({
+ description: 'Reset View',
+ event: () => {
+ this.props.Document._panX = this.props.Document._panY = 0;
+ this.props.Document[this.scaleFieldKey] = 1;
+ },
+ icon: 'compress-arrows-alt',
+ });
+ !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
+ appearanceItems.push({
+ description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
+ event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox),
+ icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
+ });
+ appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' });
+ //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
+ this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+
+ this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
+
+ // this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" });
+
+ !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
+ !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+
+ const viewctrls = ContextMenu.Instance.findByDescription('UI Controls...');
+ const viewCtrlItems = viewctrls && 'subitems' in viewctrls ? viewctrls.subitems : [];
+ !Doc.noviceMode
+ ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? 'Hide' : 'Show') + ' Snap Lines', event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: 'compress-arrows-alt' })
+ : null;
+ !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._useClusters), icon: 'braille' }) : null;
+ !viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' });
+
+ const options = ContextMenu.Instance.findByDescription('Options...');
+ const optionItems = options && 'subitems' in options ? options.subitems : [];
+ !this.props.isAnnotationOverlay &&
+ !Doc.noviceMode &&
+ optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
+ this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+ if (!Doc.noviceMode) {
+ optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
}
- !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
- const mores = ContextMenu.Instance.findByDescription("More...");
- const moreItems = mores && "subitems" in mores ? mores.subitems : [];
- if (!Doc.UserDoc().noviceMode) {
- moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
- moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+ !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
+ const mores = ContextMenu.Instance.findByDescription('More...');
+ const moreItems = mores && 'subitems' in mores ? mores.subitems : [];
+ if (!Doc.noviceMode) {
+ e.persist();
+ moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
+ moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
}
- !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
- }
+ !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
+ };
importDocument = (x: number, y: number) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = ".zip";
- input.onchange = async _e => {
- const upload = Utils.prepend("/uploadDoc");
- const formData = new FormData();
- const file = input.files && input.files[0];
- if (file) {
- formData.append('file', file);
- formData.append('remap', "true");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json !== "error") {
- const doc = await DocServer.GetRefField(json);
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.zip';
+ input.onchange = _e => {
+ input.files &&
+ Doc.importDocument(input.files[0]).then(doc => {
if (doc instanceof Doc) {
- const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
- doc.x = xx, doc.y = yy;
+ const [xx, yy] = this.getTransform().transformPoint(x, y);
+ (doc.x = xx), (doc.y = yy);
this.props.addDocument?.(doc);
- setTimeout(() =>
- SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
- docs.docs.forEach(d => LinkManager.Instance.addLink(d));
- }), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
}
- }
- }
+ });
};
input.click();
- }
+ };
+
+ @undoBatch
+ @action
+ transcribeStrokes = (math: boolean) => {
+ if (this.props.Document._isGroup && this.props.Document.transcription) {
+ if (!math) {
+ const text = StrCast(this.props.Document.transcription);
+
+ const lines = text.split('\n');
+ const height = 30 + 15 * lines.length;
+
+ this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
+ }
+ }
+ };
@action
setupDragLines = (snapToDraggedDoc: boolean = false) => {
@@ -1542,37 +1841,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
- const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect);
+ const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
- let snappableDocs = activeDocs.filter(doc => !StrListCast(doc._layerTags).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
+ let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
!snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to
!snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs
const horizLines: number[] = [];
const vertLines: number[] = [];
const invXf = this.getTransform().inverse();
- snappableDocs.filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => {
- const { left, top, width, height } = docDims(doc);
- const topLeftInScreen = invXf.transformPoint(left, top);
- const docSize = invXf.transformDirection(width, height);
+ snappableDocs
+ .filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc))
+ .forEach(doc => {
+ const { left, top, width, height } = docDims(doc);
+ const topLeftInScreen = invXf.transformPoint(left, top);
+ const docSize = invXf.transformDirection(width, height);
- horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
- vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line
- });
+ horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
+ vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line
+ });
DragManager.SetSnapLines(horizLines, vertLines);
- }
+ };
onPointerOver = (e: React.PointerEvent) => {
e.stopPropagation();
- }
+ };
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = 5;
for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) {
- this._renderCutoffData.set(unrendered[i][Id] + "", true);
+ this._renderCutoffData.set(unrendered[i][Id] + '', true);
}
}
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
@@ -1580,135 +1881,156 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
children = () => {
this.incrementalRender();
- const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
- return [
- ...children,
- ...this.views,
- <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
- ];
- }
+ const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : [];
+ return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
+ };
@computed get placeholder() {
- return <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}>
- <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
- </div>;
+ return (
+ <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}>
+ <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
+ </div>
+ );
}
@computed get marqueeView() {
TraceMobx();
- return <MarqueeView
- {...this.props}
- ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined}
- nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
- addDocTab={this.addDocTab}
- trySelectCluster={this.trySelectCluster}
- activeDocuments={this.getActiveDocuments}
- selectDocuments={this.selectDocuments}
- addDocument={this.addDocument}
- addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform}
- getTransform={this.getTransform}
- isAnnotationOverlay={this.isAnnotationOverlay}>
- <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
- {this.layoutDoc._backgroundGridShow ?
- <CollectionFreeFormBackgroundGrid
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
- panX={this.panX}
- panY={this.panY}
- zoomScaling={this.zoomScaling}
- layoutDoc={this.layoutDoc}
+ return (
+ <MarqueeView
+ {...this.props}
+ ref={this._marqueeViewRef}
+ ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined}
+ nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
+ addDocTab={this.addDocTab}
+ trySelectCluster={this.trySelectCluster}
+ activeDocuments={this.getActiveDocuments}
+ selectDocuments={this.selectDocuments}
+ addDocument={this.addDocument}
+ addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform}
+ getTransform={this.getTransform}
+ isAnnotationOverlay={this.isAnnotationOverlay}>
+ <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
+ {this.layoutDoc._backgroundGridShow ? (
+ <div>
+ <CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.props.PanelHeight}
+ panX={this.panX}
+ panY={this.panY}
+ zoomScaling={this.zoomScaling}
+ layoutDoc={this.layoutDoc}
+ isAnnotationOverlay={this.isAnnotationOverlay}
+ cachedCenteringShiftX={this.cachedCenteringShiftX}
+ cachedCenteringShiftY={this.cachedCenteringShiftY}
+ />
+ </div>
+ ) : null}
+ <CollectionFreeFormViewPannableContents
isAnnotationOverlay={this.isAnnotationOverlay}
- cachedCenteringShiftX={this.cachedCenteringShiftX}
- cachedCenteringShiftY={this.cachedCenteringShiftY}
- /> : (null)}
- <CollectionFreeFormViewPannableContents
- isAnnotationOverlay={this.isAnnotationOverlay}
- isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
- transform={this.contentTransform}
- zoomScaling={this.zoomScaling}
- presPaths={BoolCast(this.Document.presPathView)}
- progressivize={BoolCast(this.Document.editProgressivize)}
- presPinView={BoolCast(this.Document.presPinView)}
- transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, "string", null)}
- viewDefDivClick={this.props.viewDefDivClick}>
- {this.children}
- </CollectionFreeFormViewPannableContents>
- </div>
- {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
- </MarqueeView>;
- }
-
- @computed get contentScaling() {
+ isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
+ transform={this.contentTransform}
+ zoomScaling={this.zoomScaling}
+ presPaths={BoolCast(this.Document.presPathView)}
+ progressivize={BoolCast(this.Document.editProgressivize)}
+ presPinView={BoolCast(this.Document.presPinView)}
+ transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)}
+ viewDefDivClick={this.props.viewDefDivClick}>
+ {this.children}
+ </CollectionFreeFormViewPannableContents>
+ </div>
+ {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : null}
+ </MarqueeView>
+ );
+ }
+
+ @computed get nativeDimScaling() {
if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0;
const nw = this.nativeWidth;
const nh = this.nativeHeight;
const hscale = nh ? this.props.PanelHeight() / nh : 1;
const wscale = nw ? this.props.PanelWidth() / nw : 1;
- return wscale < hscale ? wscale : hscale;
+ return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
}
private groupDropDisposer?: DragManager.DragDropDisposer;
- protected createGroupEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ protected createGroupEventsTarget = (ele: HTMLDivElement) => {
+ //used for stacking and masonry view
this.groupDropDisposer?.();
if (ele) {
this.groupDropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
}
- }
+ };
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
- return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
- onPointerOver={this.onPointerOver}
- onWheel={this.onPointerWheel}
- onClick={this.onClick}
- onPointerDown={this.onPointerDown}
- onPointerMove={this.onCursorMove}
- onDrop={this.onExternalDrop.bind(this)}
- onDragOver={e => e.preventDefault()}
- onContextMenu={this.onContextMenu}
- style={{
- pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
- this.backgroundEvents ? "all" : this.props.pointerEvents as any,
- transform: `scale(${this.contentScaling || 1})`,
- width: `${100 / (this.contentScaling || 1)}%`,
- height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
- }}>
- {this._firstRender || (this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0) ?
- this.placeholder : this.marqueeView}
- {this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
-
-
- <div className={"pullpane-indicator"}
+ return (
+ <div
+ className={'collectionfreeformview-container'}
+ ref={this.createDashEventsTarget}
+ onPointerOver={this.onPointerOver}
+ onWheel={this.onPointerWheel}
+ onClick={this.onClick}
+ onPointerDown={this.onPointerDown}
+ onPointerMove={this.onCursorMove}
+ onDrop={this.onExternalDrop.bind(this)}
+ onDragOver={e => e.preventDefault()}
+ onContextMenu={this.onContextMenu}
style={{
- display: this._pullDirection ? "block" : "none",
- top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
- left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
- width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
- height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
-
+ pointerEvents:
+ this.props.Document.type === DocumentType.MARKER
+ ? 'none' // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
+ : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
+ ? 'all'
+ : (this.props.pointerEvents?.() as any),
+ transform: `scale(${this.nativeDimScaling || 1})`,
+ width: `${100 / (this.nativeDimScaling || 1)}%`,
+ height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.nativeDimScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
}}>
+ {this._firstRender ? this.placeholder : this.marqueeView}
+ {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+
+ <div
+ className={'pullpane-indicator'}
+ style={{
+ display: this._pullDirection ? 'block' : 'none',
+ top: clientRect ? (this._pullDirection === 'bottom' ? this._pullCoords[1] - clientRect.y : 0) : 'auto',
+ left: clientRect ? (this._pullDirection === 'right' ? this._pullCoords[0] - clientRect.x : 0) : 'auto',
+ width: clientRect ? (this._pullDirection === 'left' ? this._pullCoords[0] - clientRect.left : this._pullDirection === 'right' ? clientRect.right - this._pullCoords[0] : clientRect.width) : 0,
+ height: clientRect ? (this._pullDirection === 'top' ? this._pullCoords[1] - clientRect.top : this._pullDirection === 'bottom' ? clientRect.bottom - this._pullCoords[1] : clientRect.height) : 0,
+ }}></div>
+ {
+ // uncomment to show snap lines
+ <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
+ <svg style={{ width: '100%', height: '100%' }}>
+ {this._hLines?.map(l => (
+ <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
+ ))}
+ {this._vLines?.map(l => (
+ <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+ ))}
+ </svg>
+ </div>
+ }
+
+ {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
+ <div
+ className="collectionFreeForm-groupDropper"
+ ref={this.createGroupEventsTarget}
+ style={{
+ width: this.ChildDrag ? '10000' : '100%',
+ height: this.ChildDrag ? '10000' : '100%',
+ left: this.ChildDrag ? '-5000' : 0,
+ top: this.ChildDrag ? '-5000' : 0,
+ position: 'absolute',
+ background: '#0009930',
+ pointerEvents: 'all',
+ }}
+ />
+ ) : null}
</div>
- {// uncomment to show snap lines
- <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
- <svg style={{ width: "100%", height: "100%" }}>
- {this._hLines?.map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />)}
- {this._vLines?.map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)}
- </svg>
- </div>}
-
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && (this.ChildDrag || this.props.layerProvider?.(this.props.Document) === false) ?
- <div className="collectionFreeForm-groupDropper" ref={this.createGroupEventsTarget} style={{
- width: this.ChildDrag ? "10000" : "100%",
- height: this.ChildDrag ? "10000" : "100%",
- left: this.ChildDrag ? "-5000" : 0,
- top: this.ChildDrag ? "-5000" : 0,
- position: "absolute",
- background: "#0009930",
- pointerEvents: "all"
- }} /> : (null)}
- </div >;
+ );
}
}
@@ -1717,9 +2039,12 @@ interface CollectionFreeFormOverlayViewProps {
}
@observer
-class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps>{
+class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps> {
render() {
- return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele);
+ return this.props
+ .elements()
+ .filter(ele => ele.bounds?.z)
+ .map(ele => ele.ele);
}
}
@@ -1737,50 +2062,50 @@ interface CollectionFreeFormViewPannableContentsProps {
}
@observer
-class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
+class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps> {
@observable _drag: string = '';
//Adds event listener so knows pointer is down and moving
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
- this._drag = (e.target as any)?.id ?? "";
+ this._drag = (e.target as any)?.id ?? '';
document.getElementById(this._drag) && setupMoveUpEvents(e.target, e, this.onPointerMove, emptyFunction, emptyFunction);
- }
+ };
//Adjusts the value in NodeStore
@action
onPointerMove = (e: PointerEvent) => {
const doc = document.getElementById('resizable');
- const toNumber = (original: number, delta: number) => original + (delta * this.props.zoomScaling());
+ const toNumber = (original: number, delta: number) => original + delta * this.props.zoomScaling();
if (doc) {
switch (this._drag) {
- case "resizer-br":
+ case 'resizer-br':
doc.style.width = toNumber(doc.offsetWidth, e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
break;
- case "resizer-bl":
+ case 'resizer-bl':
doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
break;
- case "resizer-tr":
+ case 'resizer-tr':
doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
- case "resizer-tl":
+ case 'resizer-tl':
doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
- case "resizable":
+ case 'resizable':
doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
}
return false;
}
return true;
- }
+ };
// scale: NumCast(targetDoc._viewScale),
@computed get zoomProgressivizeContainer() {
@@ -1791,66 +2116,73 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const top = NumCast(activeItem.presPinViewY);
const width = 100;
const height = 100;
- return !this.props.presPinView ? (null) :
+ return !this.props.presPinView ? null : (
<div key="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width, height, top, left, position: 'absolute' }}>
- <div className='resizers' key={'resizer' + activeItem.id}>
- <div className='resizer top-left' onPointerDown={this.onPointerDown} />
- <div className='resizer top-right' onPointerDown={this.onPointerDown} />
- <div className='resizer bottom-left' onPointerDown={this.onPointerDown} />
- <div className='resizer bottom-right' onPointerDown={this.onPointerDown} />
+ <div className="resizers" key={'resizer' + activeItem.id}>
+ <div className="resizer top-left" onPointerDown={this.onPointerDown} />
+ <div className="resizer top-right" onPointerDown={this.onPointerDown} />
+ <div className="resizer bottom-left" onPointerDown={this.onPointerDown} />
+ <div className="resizer bottom-right" onPointerDown={this.onPointerDown} />
</div>
- </div>;
+ </div>
+ );
}
}
@computed get zoomProgressivize() {
- return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : (null);
+ return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : null;
}
@computed get progressivize() {
- return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : (null);
+ return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : null;
}
@computed get presPaths() {
- const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden");
- return !PresBox.Instance || !this.props.presPaths ? (null) : <>
- <div key="presorder">{PresBox.Instance.order}</div>
- <svg key="svg" className={presPaths}>
- <defs>
- <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
- <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
- </marker>
- <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
- <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
- </marker>
- <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
- <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
- </marker>
- </defs>
- {PresBox.Instance.paths}
- </svg>
- </>;
+ const presPaths = 'presPaths' + (this.props.presPaths ? '' : '-hidden');
+ return !PresBox.Instance || !this.props.presPaths ? null : (
+ <>
+ <div key="presorder">{PresBox.Instance.order}</div>
+ <svg key="svg" className={presPaths}>
+ <defs>
+ <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
+ <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
+ </marker>
+ <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
+ <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
+ </marker>
+ <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
+ <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
+ </marker>
+ </defs>
+ {PresBox.Instance.paths}
+ </svg>
+ </>
+ );
}
render() {
- return <div className={"collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none")}
- onScroll={e => {
- const target = e.target as any;
- if (getComputedStyle(target)?.overflow === "visible") { // if collection is visible, then scrolling will mess things up since there are no scroll bars
- target.scrollTop = target.scrollLeft = 0;
- }
- }}
- style={{
- transform: this.props.transform(),
- transition: this.props.transition,
- width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
- //willChange: "transform"
- }}>
- {this.props.children()}
- {this.presPaths}
- {this.progressivize}
- {this.zoomProgressivize}
- </div>;
+ return (
+ <div
+ className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
+ onScroll={e => {
+ const target = e.target as any;
+ if (getComputedStyle(target)?.overflow === 'visible') {
+ // if collection is visible, then scrolling will mess things up since there are no scroll bars
+ target.scrollTop = target.scrollLeft = 0;
+ }
+ }}
+ style={{
+ transform: this.props.transform(),
+ transition: this.props.transition,
+ width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
+ //willChange: "transform"
+ }}>
+ {this.props.children()}
+ {this.presPaths}
+ {this.progressivize}
+ {this.zoomProgressivize}
+ </div>
+ );
}
}
@@ -1867,44 +2199,73 @@ interface CollectionFreeFormViewBackgroundGridProps {
}
@observer
class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
-
-
chooseGridSpace = (gridSpace: number): number => {
if (!this.props.zoomScaling()) return 50;
const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3;
return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10);
- }
+ };
render() {
- const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc["_backgroundGrid-spacing"], 50));
- const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.props.panX() % gridSpace - gridSpace) * this.props.zoomScaling();
- const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.props.panY() % gridSpace - gridSpace) * this.props.zoomScaling();
+ const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50));
+ const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
+ const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
const renderGridSpace = gridSpace * this.props.zoomScaling();
const w = this.props.PanelWidth() + 2 * renderGridSpace;
const h = this.props.PanelHeight() + 2 * renderGridSpace;
- const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
- return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
- ref={(el) => {
- const ctx = el?.getContext('2d');
- if (ctx) {
- const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
- const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
- ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
- ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
- ctx.clearRect(0, 0, w, h);
+ const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
+ return (
+ <canvas
+ className="collectionFreeFormView-grid"
+ width={w}
+ height={h}
+ style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
+ ref={el => {
+ const ctx = el?.getContext('2d');
if (ctx) {
- ctx.strokeStyle = strokeStyle;
- ctx.beginPath();
- for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
- ctx.moveTo(x, Cy - h);
- ctx.lineTo(x, Cy + h);
- }
- for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
- ctx.moveTo(Cx - w, y);
- ctx.lineTo(Cx + w, y);
+ const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
+ const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
+ ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
+ ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
+ ctx.clearRect(0, 0, w, h);
+ if (ctx) {
+ ctx.strokeStyle = strokeStyle;
+ ctx.beginPath();
+ for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
+ ctx.moveTo(x, Cy - h);
+ ctx.lineTo(x, Cy + h);
+ }
+ for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+ ctx.moveTo(Cx - w, y);
+ ctx.lineTo(Cx + w, y);
+ }
+ ctx.stroke();
}
- ctx.stroke();
}
- }
- }} />;
+ }}
+ />
+ );
}
-} \ No newline at end of file
+}
+
+export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
+ SelectionManager.DeselectAll();
+ dv.props.focus(dv.props.Document, {
+ willZoom: true,
+ afterFocus: async didMove => {
+ if (!didMove) {
+ const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+ const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
+ ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5);
+ }
+ return ViewAdjustment.doNothing;
+ },
+ });
+ Doc.linkFollowHighlight(dv?.props.Document, false);
+}
+ScriptingGlobals.add(CollectionBrowseClick);
+ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
+ !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
+});
+ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
+ !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
+});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 1f59f9732..8a8b528f6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -14,7 +14,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction;
public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
- public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public showMarquee: () => void = unimplementedFunction;
public hideMarquee: () => void = unimplementedFunction;
public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
@@ -64,16 +63,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
</button>
</Tooltip>,
];
- if (false && !SelectionManager.Views().some(v => v.props.Document.type !== DocumentType.INK)) {
- buttons.push(
- <Tooltip key="inkToText" title={<div className="dash-tooltip">Change to Text</div>} placement="bottom">
- <button
- className="antimodeMenu-button"
- onPointerDown={this.inkToText}>
- <FontAwesomeIcon icon="font" size="lg" />
- </button>
- </Tooltip>);
- }
return this.getElement(buttons);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 62510ce9d..41e4d6b6a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -10,6 +10,7 @@
user-select: none;
}
+
.marqueeView:focus-within {
overflow: hidden;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b10b0912f..65a11cbcb 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,38 +1,33 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool } from "../../../../fields/InkField";
-import { List } from "../../../../fields/List";
-import { RichTextField } from "../../../../fields/RichTextField";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
-import { GetEffectiveAcl } from "../../../../fields/util";
-import { Utils, intersectRect, returnFalse } from "../../../../Utils";
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { ContextMenu } from "../../ContextMenu";
-import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { PresBox } from "../../nodes/trails/PresBox";
-import { PresMovement } from "../../nodes/trails/PresEnums";
-import { PreviewCursor } from "../../PreviewCursor";
-import { CollectionDockingView } from "../CollectionDockingView";
-import { SubCollectionViewProps } from "../CollectionSubView";
-import { CollectionView } from "../CollectionView";
-import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
-import "./MarqueeView.scss";
-import React = require("react");
-import { StyleLayers } from "../../StyleProvider";
-import { TreeView } from "../TreeView";
-import { VideoBox } from "../../nodes/VideoBox";
-import { ImageField, WebField } from "../../../../fields/URLField";
-import { pasteImageBitmap } from "../../nodes/WebBoxRenderer";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, Opt } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { InkData, InkField, InkTool } from '../../../../fields/InkField';
+import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { GetEffectiveAcl } from '../../../../fields/util';
+import { intersectRect, returnFalse, Utils } from '../../../../Utils';
+import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
+import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { PinViewProps, PresBox } from '../../nodes/trails/PresBox';
+import { VideoBox } from '../../nodes/VideoBox';
+import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
+import { PreviewCursor } from '../../PreviewCursor';
+import { SubCollectionViewProps } from '../CollectionSubView';
+import { TabDocView } from '../TabDocView';
+import { TreeView } from '../TreeView';
+import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
+import './MarqueeView.scss';
+import React = require('react');
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -46,27 +41,45 @@ interface MarqueeViewProps {
ungroup?: () => void;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
}
+
+export interface MarqueeViewBounds {
+ left: number;
+ top: number;
+ width: number;
+ height: number;
+}
+
@observer
-export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps>
-{
+export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> {
private _commandExecuted = false;
@observable _lastX: number = 0;
@observable _lastY: number = 0;
@observable _downX: number = 0;
@observable _downY: number = 0;
- @observable _visible: boolean = false;
+ @observable _visible: boolean = false; // selection rentangle for marquee selection/free hand lasso is visible
@observable _lassoPts: [number, number][] = [];
@observable _lassoFreehand: boolean = false;
- @computed get Transform() { return this.props.getTransform(); }
+ @computed get Transform() {
+ return this.props.getTransform();
+ }
@computed get Bounds() {
+ // nda - ternary argument to transformPoint is returning the lower of the downX/Y and lastX/Y and passing in as args x,y
const topLeft = this.Transform.transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+ // nda - args to transformDirection is just x and y diff btw downX/Y and lastX/Y
const size = this.Transform.transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ const bounds: MarqueeViewBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ return bounds;
+ }
+ get inkDoc() {
+ return this.props.Document;
+ }
+ get ink() {
+ return Cast(this.props.Document.ink, InkField);
+ }
+ set ink(value: Opt<InkField>) {
+ this.props.Document.ink = value;
}
- get inkDoc() { return this.props.Document; }
- get ink() { return Cast(this.props.Document.ink, InkField); }
- set ink(value: Opt<InkField>) { this.props.Document.ink = value; }
componentDidMount() {
this.props.setPreviewCursor?.(this.setPreviewCursor);
@@ -75,14 +88,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
cleanupInteractions = (all: boolean = false, hideMarquee: boolean = true) => {
if (all) {
- document.removeEventListener("pointerup", this.onPointerUp, true);
- document.removeEventListener("pointermove", this.onPointerMove, true);
+ document.removeEventListener('pointerup', this.onPointerUp, true);
+ document.removeEventListener('pointermove', this.onPointerMove, true);
}
- document.removeEventListener("keydown", this.marqueeCommand, true);
+ document.removeEventListener('keydown', this.marqueeCommand, true);
hideMarquee && this.hideMarquee();
this._lassoPts = [];
- }
+ };
@undoBatch
@action
@@ -91,76 +104,75 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// tslint:disable-next-line:prefer-const
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
- if (e.key === "?") {
- cm.setDefaultItem("?", (str: string) => this.props.addDocTab(
- Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: "bing", useCors: true }), "add:right"));
+ if (e.key === '?') {
+ cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), 'add:right'));
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
- } else
- if (e.key === "u" && this.props.ungroup) {
- e.stopPropagation();
- this.props.ungroup();
- }
- else if (e.key === ":") {
- DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y);
+ } else if (e.key === 'u' && this.props.ungroup) {
+ e.stopPropagation();
+ this.props.ungroup();
+ } else if (e.key === ':') {
+ DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y);
- cm.displayMenu(this._downX, this._downY, undefined, true);
- e.stopPropagation();
- } else if (e.key === "a" && (e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.props.selectDocuments(this.props.activeDocuments());
- e.stopPropagation();
- } else if (e.key === "q" && e.ctrlKey) {
- e.preventDefault();
- (async () => {
- const text: string = await navigator.clipboard.readText();
- const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
- for (let i = 0; i < ns.length - 1; i++) {
- while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
- ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
- ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
- ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) {
- const sub = ns[i].endsWith("\r") ? 1 : 0;
- const br = ns[i + 1].trim() === "";
- ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
- if (br) break;
- }
+ cm.displayMenu(this._downX, this._downY, undefined, true);
+ e.stopPropagation();
+ } else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ this.props.selectDocuments(this.props.activeDocuments());
+ e.stopPropagation();
+ } else if (e.key === 'q' && e.ctrlKey) {
+ e.preventDefault();
+ (async () => {
+ const text: string = await navigator.clipboard.readText();
+ const ns = text.split('\n').filter(t => t.trim() !== '\r' && t.trim() !== '');
+ for (let i = 0; i < ns.length - 1; i++) {
+ while (
+ !(ns[i].trim() === '' || ns[i].endsWith('-\r') || ns[i].endsWith('-') || ns[i].endsWith(';\r') || ns[i].endsWith(';') || ns[i].endsWith('.\r') || ns[i].endsWith('.') || ns[i].endsWith(':\r') || ns[i].endsWith(':')) &&
+ i < ns.length - 1
+ ) {
+ const sub = ns[i].endsWith('\r') ? 1 : 0;
+ const br = ns[i + 1].trim() === '';
+ ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
+ if (br) break;
}
- let ypos = y;
- ns.map(line => {
- const indent = line.search(/\S|$/);
- const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: ypos, title: line });
- this.props.addDocument?.(newBox);
- ypos += 40 * this.Transform.Scale;
- });
- })();
- e.stopPropagation();
- } else if (e.key === "b" && e.ctrlKey) {
- document.body.focus(); // so that we can access the clipboard without an error
- setTimeout(() =>
- pasteImageBitmap((data: any, error: any) => {
- error && console.log(error);
- data && VideoBox.convertDataUri(data, this.props.Document[Id] + "-thumb-frozen").then(returnedfilename => {
- this.props.Document["thumb-frozen"] = new ImageField(returnedfilename);
+ }
+ let ypos = y;
+ ns.map(line => {
+ const indent = line.search(/\S|$/);
+ const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
+ this.props.addDocument?.(newBox);
+ ypos += 40 * this.Transform.Scale;
+ });
+ })();
+ e.stopPropagation();
+ } else if (e.key === 'b' && e.ctrlKey) {
+ document.body.focus(); // so that we can access the clipboard without an error
+ setTimeout(() =>
+ pasteImageBitmap((data: any, error: any) => {
+ error && console.log(error);
+ data &&
+ VideoBox.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+ this.props.Document['thumb-frozen'] = new ImageField(returnedfilename);
});
- }));
- } else if (e.key === "s" && e.ctrlKey) {
- e.preventDefault();
- const slide = Doc.copyDragFactory(Doc.UserDoc().emptySlide as Doc)!;
- slide.x = x;
- slide.y = y;
- FormattedTextBox.SelectOnLoad = slide[Id];
- TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
- this.props.addDocument?.(slide);
- e.stopPropagation();
- } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
- FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : "";
- FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch");
- this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xPadding === 0));
- e.stopPropagation();
- }
- }
+ })
+ );
+ } else if (e.key === 's' && e.ctrlKey) {
+ e.preventDefault();
+ const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!;
+ slide.x = x;
+ slide.y = y;
+ FormattedTextBox.SelectOnLoad = slide[Id];
+ TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
+ this.props.addDocument?.(slide);
+ e.stopPropagation();
+ } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
+ FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : '';
+ FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('live text batch');
+ this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0));
+ e.stopPropagation();
+ }
+ };
//heuristically converts pasted text into a table.
// assumes each entry is separated by a tab
// skips all rows until it gets to a row with more than one entry
@@ -169,26 +181,26 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header
// assumes each cell is a string or a number
pasteTable(ns: string[], x: number, y: number) {
- while (ns.length > 0 && ns[0].split("\t").length < 2) {
+ while (ns.length > 0 && ns[0].split('\t').length < 2) {
ns.splice(0, 1);
}
if (ns.length > 0) {
- const columns = ns[0].split("\t");
+ const columns = ns[0].split('\t');
const docList: Doc[] = [];
- let groupAttr: string | number = "";
+ let groupAttr: string | number = '';
const rowProto = new Doc();
rowProto.title = rowProto.Id;
rowProto._width = 200;
rowProto.isPrototype = true;
for (let i = 1; i < ns.length - 1; i++) {
- const values = ns[i].split("\t");
+ const values = ns[i].split('\t');
if (values.length === 1 && columns.length > 1) {
groupAttr = values[0];
continue;
}
const docDataProto = Doc.MakeDelegate(rowProto);
docDataProto.isPrototype = true;
- columns.forEach((col, i) => docDataProto[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
+ columns.forEach((col, i) => (docDataProto[columns[i]] = values.length > i ? (values[i].indexOf(Number(values[i]).toString()) !== -1 ? Number(values[i]) : values[i]) : undefined));
if (groupAttr) {
docDataProto._group = groupAttr;
}
@@ -197,7 +209,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
doc._width = 200;
docList.push(doc);
}
- const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", _width: 300, _height: 100 });
+ const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField('_group', '#f1efeb')] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, '#f1efeb'))], docList, {
+ x: x,
+ y: y,
+ title: 'droppedTable',
+ _width: 300,
+ _height: 100,
+ });
this.props.addDocument?.(newCol);
}
@@ -209,8 +227,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downY = this._lastY = e.clientY;
if (!(e.nativeEvent as any).marqueeHit) {
(e.nativeEvent as any).marqueeHit = true;
- // allow marquee if right click OR alt+left click
- if (e.button === 2 || (e.button === 0 && e.altKey)) {
+ // allow marquee if right click OR alt+left click OR in adding presentation slide & left key drag mode
+ if (e.button === 2 || (e.button === 0 && e.altKey) || (PresBox.startMarquee && e.button === 0)) {
// if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
this.setPreviewCursor(e.clientX, e.clientY, true, false);
// (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
@@ -218,10 +236,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// }
// bcz: do we need this? it kills the context menu on the main collection if !altKey
// e.stopPropagation();
- }
- else PreviewCursor.Visible = false;
+ } else PreviewCursor.Visible = false;
}
- }
+ };
@action
onPointerMove = (e: PointerEvent): void => {
@@ -229,8 +246,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._lastY = e.pageY;
this._lassoPts.push([e.clientX, e.clientY]);
if (!e.cancelBubble) {
- if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD ||
- Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
+ if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
if (!this._commandExecuted) {
this.showMarquee();
}
@@ -241,7 +257,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
}
e.altKey && e.preventDefault();
- }
+ if (PresBox.startMarquee) {
+ e.stopPropagation();
+ }
+ };
@action
onPointerUp = (e: PointerEvent): void => {
@@ -258,31 +277,37 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const hideMarquee = () => {
this.hideMarquee();
MarqueeOptionsMenu.Instance.fadeOut(true);
- document.removeEventListener("pointerdown", hideMarquee);
- document.removeEventListener("wheel", hideMarquee);
+ document.removeEventListener('pointerdown', hideMarquee, true);
+ document.removeEventListener('wheel', hideMarquee, true);
};
- if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
+ if (PresBox.startMarquee) {
+ this.pinWithView();
+ PresBox.startMarquee = false;
+ }
+ if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100 && !PresBox.startMarquee) {
MarqueeOptionsMenu.Instance.createCollection = this.collection;
MarqueeOptionsMenu.Instance.delete = this.delete;
MarqueeOptionsMenu.Instance.summarize = this.summary;
- MarqueeOptionsMenu.Instance.inkToText = this.syntaxHighlight;
MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
MarqueeOptionsMenu.Instance.pinWithView = this.pinWithView;
- document.addEventListener("pointerdown", hideMarquee);
- document.addEventListener("wheel", hideMarquee);
+ document.addEventListener('pointerdown', hideMarquee, true);
+ document.addEventListener('wheel', hideMarquee, true);
} else {
this.hideMarquee();
}
this.cleanupInteractions(true, this._commandExecuted);
e.altKey && e.preventDefault();
- }
+ };
clearSelection() {
- if (window.getSelection) { window.getSelection()?.removeAllRanges(); }
- else if (document.getSelection()) { document.getSelection()?.empty(); }
+ if (window.getSelection) {
+ window.getSelection()?.removeAllRanges();
+ } else if (document.getSelection()) {
+ document.getSelection()?.empty();
+ }
}
setPreviewCursor = action((x: number, y: number, drag: boolean, hide: boolean) => {
@@ -297,9 +322,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._commandExecuted = false;
PreviewCursor.Visible = false;
this.cleanupInteractions(true);
- document.addEventListener("pointermove", this.onPointerMove, true);
- document.addEventListener("pointerup", this.onPointerUp, true);
- document.addEventListener("keydown", this.marqueeCommand, true);
+ document.addEventListener('pointermove', this.onPointerMove, true);
+ document.addEventListener('pointerup', this.onPointerUp, true);
+ document.addEventListener('keydown', this.marqueeCommand, true);
} else {
this._downX = x;
this._downY = y;
@@ -313,9 +338,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onClick = (e: React.MouseEvent): void => {
- if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
- Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
- if (CurrentUserUtils.SelectedTool === InkTool.None) {
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (Doc.ActiveTool === InkTool.None) {
if (!(e.nativeEvent as any).marqueeHit) {
(e.nativeEvent as any).marqueeHit = true;
if (!this.props.trySelectCluster(e.shiftKey)) {
@@ -324,17 +348,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
}
// let the DocumentView stopPropagation of this event when it selects this document
- } else { // why do we get a click event when the cursor have moved a big distance?
+ } else {
+ // why do we get a click event when the cursor have moved a big distance?
// let's cut it off here so no one else has to deal with it.
e.stopPropagation();
}
- }
+ };
@action
- showMarquee = () => { this._visible = true; }
-
+ showMarquee = () => (this._visible = true);
@action
- hideMarquee = () => { this._visible = false; }
+ hideMarquee = () => (this._visible = false);
@undoBatch
@action
@@ -346,25 +370,26 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- }
-
- getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, layers: string[], makeGroup: Opt<boolean>) => {
- const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => {
- Doc.GetProto(doc).data = new List<Doc>(selected);
- Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform";
- !this.props.isAnnotationOverlay && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(doc));
- doc._panX = doc._panY = 0;
- return doc;
- })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
+ };
+
+ getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => {
+ const newCollection = creator
+ ? creator(selected, { title: 'nested stack' })
+ : ((doc: Doc) => {
+ Doc.GetProto(doc).data = new List<Doc>(selected);
+ Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
+ !this.props.isAnnotationOverlay && Doc.AddDocToList(Doc.MyFileOrphans, undefined, Doc.GetProto(doc));
+ doc._panX = doc._panY = 0;
+ return doc;
+ })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
newCollection.system = undefined;
- newCollection._layerTags = new List<string>(layers);
newCollection._width = this.Bounds.width;
newCollection._height = this.Bounds.height;
newCollection._isGroup = makeGroup;
newCollection.forceActive = makeGroup;
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
- selected.forEach(d => d.context = newCollection);
+ selected.forEach(d => (d.context = newCollection));
this.hideMarquee();
return newCollection;
});
@@ -374,95 +399,81 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
selected.forEach(d => this.props.removeDocument?.(d));
- const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
+ const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
this.props.addDocument?.(newCollection);
this.props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- }
-
+ };
+
+ /**
+ * This triggers the TabDocView.PinDoc method which is the universal method
+ * used to pin documents to the currently active presentation trail.
+ *
+ * This one is unique in that it includes the bounds associated with marquee view.
+ */
@undoBatch
@action
- pinWithView = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ pinWithView = async () => {
+ const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height);
const doc = this.props.Document;
- const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
- if (curPres) {
- if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; }
- const pinDoc = Doc.MakeAlias(doc);
- pinDoc.presentationTargetDoc = doc;
- pinDoc.presMovement = PresMovement.Zoom;
- pinDoc.groupWithUp = false;
- pinDoc.context = curPres;
- pinDoc.title = doc.title + " - Slide";
- const presArray = PresBox.Instance?.sortArray();
- const size = PresBox.Instance?._selectedArray.size;
- const presSelected = presArray && size ? presArray[size - 1] : undefined;
- Doc.AddDocToList(curPres, "data", pinDoc, presSelected);
- if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
- if (!DocumentManager.Instance.getDocumentView(curPres)) {
- CollectionDockingView.AddSplit(curPres, "right");
- }
- PresBox.Instance?._selectedArray.clear();
- pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Updates selected array
- const index = PresBox.Instance?.childDocs.indexOf(pinDoc);
- index && (curPres._itemIndex = index);
- if (e instanceof KeyboardEvent ? e.key === "c" : true) {
- const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height);
- pinDoc.presPinView = true;
- pinDoc.presPinViewX = this.Bounds.left + this.Bounds.width / 2;
- pinDoc.presPinViewY = this.Bounds.top + this.Bounds.height / 2;
- pinDoc.presPinViewScale = scale;
- }
- }
+ const viewOptions: PinViewProps = {
+ bounds: this.Bounds,
+ scale: scale,
+ };
+ TabDocView.PinDoc(doc, { pinWithView: viewOptions });
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- }
+ };
@undoBatch
@action
collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => {
const selected = this.marqueeSelect(false);
- if (e instanceof KeyboardEvent ? "cg".includes(e.key) : true) {
- selected.map(action(d => {
- const dx = NumCast(d.x);
- const dy = NumCast(d.y);
- delete d.x;
- delete d.y;
- delete d.activeFrame;
- delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
- delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
- d.x = dx - this.Bounds.left - this.Bounds.width / 2;
- d.y = dy - this.Bounds.top - this.Bounds.height / 2;
- return d;
- }));
+ if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
+ selected.map(
+ action(d => {
+ const dx = NumCast(d.x);
+ const dy = NumCast(d.y);
+ delete d.x;
+ delete d.y;
+ delete d.activeFrame;
+ delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ d.x = dx - this.Bounds.left - this.Bounds.width / 2;
+ d.y = dy - this.Bounds.top - this.Bounds.height / 2;
+ return d;
+ })
+ );
this.props.removeDocument?.(selected);
}
- const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined, [], group);
+ // TODO: nda - this is the code to actually get a new grouped collection
+ const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group);
this.props.addDocument?.(newCollection);
this.props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
- }
+ };
@undoBatch
@action
syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
- if (e instanceof KeyboardEvent ? e.key === "i" : true) {
+ if (e instanceof KeyboardEvent ? e.key === 'i' : true) {
const inks = selected.filter(s => s.type === DocumentType.INK);
const setDocs = selected.filter(s => s.type === DocumentType.RTF && s.color);
- const sets = setDocs.map((sd) => Cast(sd.data, RichTextField)?.Text as string);
+ const sets = setDocs.map(sd => Cast(sd.data, RichTextField)?.Text as string);
const colors = setDocs.map(sd => FieldValue(sd.color) as string);
const wordToColor = new Map<string, string>();
- sets.forEach((st: string, i: number) => st.split(",").forEach(word => wordToColor.set(word, colors[i])));
+ sets.forEach((st: string, i: number) => st.split(',').forEach(word => wordToColor.set(word, colors[i])));
const strokes: InkData[] = [];
inks.filter(i => Cast(i.data, InkField)).forEach(i => {
const d = Cast(i.data, InkField, null);
- const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]);
- const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]);
+ const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
+ const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top })));
});
- CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
+ CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {
// const wordResults = results.filter((r: any) => r.category === "inkWord");
// for (const word of wordResults) {
// const indices: number[] = word.strokeIds;
@@ -503,12 +514,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// }
// });
// }
- const lines = results.filter((r: any) => r.category === "line");
- const text = lines.map((l: any) => l.recognizedText).join("\r\n");
+ const lines = results.filter((r: any) => r.category === 'line');
+ const text = lines.map((l: any) => l.recognizedText).join('\r\n');
this.props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
});
}
- }
+ };
@undoBatch
@action
@@ -519,53 +530,50 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
d.y = NumCast(d.y) - this.Bounds.top;
return d;
});
- const summary = Docs.Create.TextDocument("", { x: this.Bounds.left, y: this.Bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
- const portal = Doc.MakeAlias(summary);
- Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected);
- Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations");
- summary._backgroundColor = "#e2ad32";
- portal.layoutKey = "layout_portal";
- portal.title = "document collection";
- DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", "");
+ const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' });
+ const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+ DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', '');
+ portal.hidden = true;
+ this.props.addDocument?.(portal);
this.props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
- }
+ };
@action
background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
- const newCollection = this.getCollection([], undefined, [StyleLayers.Background], undefined);
+ const newCollection = this.getCollection([], undefined, undefined);
this.props.addDocument?.(newCollection);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
setTimeout(() => this.props.selectDocuments([newCollection]));
- }
+ };
@undoBatch
marqueeCommand = action((e: KeyboardEvent) => {
if (this._commandExecuted || (e as any).propagationIsStopped) {
return;
}
- if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
+ if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd') {
this._commandExecuted = true;
e.stopPropagation();
(e as any).propagationIsStopped = true;
this.delete();
e.stopPropagation();
}
- if ("cbtsSpg".indexOf(e.key) !== -1) {
+ if ('cbtsSpg'.indexOf(e.key) !== -1) {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
(e as any).propagationIsStopped = true;
- if (e.key === "g") this.collection(e, true);
- if (e.key === "c" || e.key === "t") this.collection(e);
- if (e.key === "s" || e.key === "S") this.summary(e);
- if (e.key === "b") this.background(e);
- if (e.key === "p") this.pileup(e);
+ if (e.key === 'g') this.collection(e, true);
+ if (e.key === 'c' || e.key === 't') this.collection(e);
+ if (e.key === 's' || e.key === 'S') this.summary(e);
+ if (e.key === 'b') this.background(e);
+ if (e.key === 'p') this.pileup(e);
this.cleanupInteractions(false);
}
- if (e.key === "r" || e.key === " ") {
+ if (e.key === 'r' || e.key === ' ') {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
@@ -573,18 +581,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
});
- touchesLine(r1: { left: number, top: number, width: number, height: number }) {
+ touchesLine(r1: { left: number; top: number; width: number; height: number }) {
for (const lassoPt of this._lassoPts) {
const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
- if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width &&
- r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) {
+ if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) {
return true;
}
}
return false;
}
- boundingShape(r1: { left: number, top: number, width: number, height: number }) {
+ boundingShape(r1: { left: number; top: number; width: number; height: number }) {
const xs = this._lassoPts.map(pair => pair[0]);
const ys = this._lassoPts.map(pair => pair[1]);
const tl = this.Transform.transformPoint(Math.min(...xs), Math.min(...ys));
@@ -597,10 +604,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
let hasRight = false;
for (const lassoPt of this._lassoPts) {
const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
- hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
- hasTop = hasTop || (truePoint[1] > tl[1] && truePoint[1] < r1.top) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width);
- hasRight = hasRight || (truePoint[0] < br[0] && truePoint[0] > r1.left + r1.width) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
- hasBottom = hasBottom || (truePoint[1] < br[1] && truePoint[1] > r1.top + r1.height) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width);
+ hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
+ hasTop = hasTop || (truePoint[1] > tl[1] && truePoint[1] < r1.top && truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width);
+ hasRight = hasRight || (truePoint[0] < br[0] && truePoint[0] > r1.left + r1.width && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
+ hasBottom = hasBottom || (truePoint[1] < br[1] && truePoint[1] > r1.top + r1.height && truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width);
if (hasTop && hasLeft && hasBottom && hasRight) {
return true;
}
@@ -620,9 +627,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
(this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc);
}
};
- this.props.activeDocuments().filter(doc => this.props.layerProvider?.(doc) !== false && !doc.z).map(selectFunc);
- if (!selection.length && selectBackgrounds) this.props.activeDocuments().filter(doc => doc.z === undefined).map(selectFunc);
- if (!selection.length) this.props.activeDocuments().filter(doc => doc.z !== undefined).map(selectFunc);
+ this.props
+ .activeDocuments()
+ .filter(doc => !doc.z && !doc._lockedPosition)
+ .map(selectFunc);
+ if (!selection.length && selectBackgrounds)
+ this.props
+ .activeDocuments()
+ .filter(doc => doc.z === undefined)
+ .map(selectFunc);
+ if (!selection.length)
+ this.props
+ .activeDocuments()
+ .filter(doc => doc.z !== undefined)
+ .map(selectFunc);
return selection;
}
@@ -630,31 +648,42 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const cpt = this._lassoFreehand || !this._visible ? [0, 0] : [this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY];
const p = this.props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
const v = this._lassoFreehand ? [0, 0] : this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- return <div className="marquee" style={{
- transform: `translate(${p[0]}px, ${p[1]}px)`,
- width: Math.abs(v[0]),
- height: Math.abs(v[1]),
- zIndex: 2000
- }}> {this._lassoFreehand ?
- <svg height={2000} width={2000}>
- <polyline points={this._lassoPts.reduce((s, pt) => s + pt[0] + "," + pt[1] + " ", "")} fill="none" stroke="black" strokeWidth="1" strokeDasharray="3" />
- </svg>
- :
- <span className="marquee-legend" />}
- </div>;
+ return (
+ <div
+ className="marquee"
+ style={{
+ transform: `translate(${p[0]}px, ${p[1]}px)`,
+ width: Math.abs(v[0]),
+ height: Math.abs(v[1]),
+ zIndex: 2000,
+ }}>
+ {' '}
+ {this._lassoFreehand ? (
+ <svg height={2000} width={2000}>
+ <polyline points={this._lassoPts.reduce((s, pt) => s + pt[0] + ',' + pt[1] + ' ', '')} fill="none" stroke="black" strokeWidth="1" strokeDasharray="3" />
+ </svg>
+ ) : (
+ <span className="marquee-legend" />
+ )}
+ </div>
+ );
}
render() {
- return <div className="marqueeView"
- style={{
- overflow: StrCast(this.props.Document._overflow),
- cursor: CurrentUserUtils.SelectedTool === InkTool.Pen ? "crosshair" : "pointer"
- }}
-
- onDragOver={e => e.preventDefault()}
- onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
- {this._visible ? this.marqueeDiv : null}
- {this.props.children}
- </div>;
+ return (
+ <div
+ className="marqueeView"
+ style={{
+ overflow: StrCast(this.props.Document._overflow),
+ cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible || PresBox.startMarquee ? 'crosshair' : 'pointer',
+ }}
+ onDragOver={e => e.preventDefault()}
+ onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)}
+ onClick={this.onClick}
+ onPointerDown={this.onPointerDown}>
+ {this._visible ? this.marqueeDiv : null}
+ {this.props.children}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index a6171af51..845b07c51 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -13,6 +13,10 @@
display: flex;
flex-direction: row;
+ .document-wrapper:hover {
+ z-index: 2000;
+ }
+
.react-grid-layout {
width: 100%;
}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 58ea7410d..9468c5f06 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,6 +1,6 @@
import { action, computed, Lambda, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import * as React from "react";
+import * as React from 'react';
import { Doc, Opt } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -15,8 +15,8 @@ import { ContextMenuProps } from '../../ContextMenuItem';
import { DocumentView } from '../../nodes/DocumentView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { CollectionSubView } from '../CollectionSubView';
-import "./CollectionGridView.scss";
-import Grid, { Layout } from "./Grid";
+import './CollectionGridView.scss';
+import Grid, { Layout } from './Grid';
@observer
export class CollectionGridView extends CollectionSubView() {
@@ -29,50 +29,76 @@ export class CollectionGridView extends CollectionSubView() {
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
- @computed get numCols() { return NumCast(this.props.Document.gridNumCols, 10); }
- @computed get rowHeight() { return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight; }
- // sets the default width and height of the grid nodes
- @computed get defaultW() { return NumCast(this.props.Document.gridDefaultW, 2); }
- @computed get defaultH() { return NumCast(this.props.Document.gridDefaultH, 2); }
+ @computed get numCols() {
+ return NumCast(this.props.Document.gridNumCols, 10);
+ }
+ @computed get rowHeight() {
+ return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight;
+ }
+ // sets the default width and height of the grid nodes
+ @computed get defaultW() {
+ return NumCast(this.props.Document.gridDefaultW, 2);
+ }
+ @computed get defaultH() {
+ return NumCast(this.props.Document.gridDefaultH, 2);
+ }
- @computed get colWidthPlusGap() { return (this.props.PanelWidth() - this.margin) / this.numCols; }
- @computed get rowHeightPlusGap() { return this.rowHeight + this.margin; }
+ @computed get colWidthPlusGap() {
+ return (this.props.PanelWidth() - this.margin) / this.numCols;
+ }
+ @computed get rowHeightPlusGap() {
+ return this.rowHeight + this.margin;
+ }
- @computed get margin() { return NumCast(this.props.Document.margin, 10); } // sets the margin between grid nodes
+ @computed get margin() {
+ return NumCast(this.props.Document.margin, 10);
+ } // sets the margin between grid nodes
- @computed get flexGrid() { return BoolCast(this.props.Document.gridFlex, true); } // is grid static/flexible i.e. whether nodes be moved around and resized
- @computed get compaction() { return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, "vertical")); } // is grid static/flexible i.e. whether nodes be moved around and resized
+ @computed get flexGrid() {
+ return BoolCast(this.props.Document.gridFlex, true);
+ } // is grid static/flexible i.e. whether nodes be moved around and resized
+ @computed get compaction() {
+ return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, 'vertical'));
+ } // is grid static/flexible i.e. whether nodes be moved around and resized
/**
* Sets up the listeners for the list of documents and the reset button.
*/
componentDidMount() {
- this._changeListenerDisposer = reaction(() => this.childLayoutPairs, (pairs) => {
- const newLayouts: Layout[] = [];
- const oldLayouts = this.savedLayoutList;
- pairs.forEach((pair, i) => {
- const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
- if (existing) newLayouts.push(existing);
- else {
- if (Object.keys(this.dropLocation).length) { // external drop
- this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number, y: number }, !this.flexGrid));
- this.dropLocation = {};
- }
- else { // internal drop
- this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ this._changeListenerDisposer = reaction(
+ () => this.childLayoutPairs,
+ pairs => {
+ const newLayouts: Layout[] = [];
+ const oldLayouts = this.savedLayoutList;
+ pairs.forEach((pair, i) => {
+ const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
+ if (existing) newLayouts.push(existing);
+ else {
+ if (Object.keys(this.dropLocation).length) {
+ // external drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
+ this.dropLocation = {};
+ } else {
+ // internal drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ }
}
- }
- });
- pairs?.length && this.setLayoutList(newLayouts);
- }, { fireImmediately: true });
+ });
+ pairs?.length && this.setLayoutList(newLayouts);
+ },
+ { fireImmediately: true }
+ );
// updates the layouts if the reset button has been clicked
- this._resetListenerDisposer = reaction(() => this.props.Document.gridResetLayout, (reset) => {
- if (reset && this.flexGrid) {
- this.setLayout(this.childLayoutPairs.map((pair, index) => this.makeLayoutItem(pair.layout, this.unflexedPosition(index))));
+ this._resetListenerDisposer = reaction(
+ () => this.props.Document.gridResetLayout,
+ reset => {
+ if (reset && this.flexGrid) {
+ this.setLayout(this.childLayoutPairs.map((pair, index) => this.makeLayoutItem(pair.layout, this.unflexedPosition(index))));
+ }
+ this.props.Document.gridResetLayout = false;
}
- this.props.Document.gridResetLayout = false;
- });
+ );
}
/**
@@ -85,15 +111,15 @@ export class CollectionGridView extends CollectionSubView() {
/**
* @returns the default location of the grid node (i.e. when the grid is static)
- * @param index
+ * @param index
*/
- unflexedPosition(index: number): Omit<Layout, "i"> {
+ unflexedPosition(index: number): Omit<Layout, 'i'> {
return {
x: (index % (Math.floor(this.numCols / this.defaultW) || 1)) * this.defaultW,
y: Math.floor(index / (Math.floor(this.numCols / this.defaultH) || 1)) * this.defaultH,
w: this.defaultW,
h: this.defaultH,
- static: true
+ static: true,
};
}
@@ -110,9 +136,9 @@ export class CollectionGridView extends CollectionSubView() {
/**
* Creates a layout object for a grid item
*/
- makeLayoutItem = (doc: Doc, pos: { x: number, y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
- return ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
- }
+ makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
+ return { i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static };
+ };
/**
* Adds a layout to the list of layouts.
@@ -122,16 +148,16 @@ export class CollectionGridView extends CollectionSubView() {
f !== -1 && layouts.splice(f, 1);
layouts.push(layout);
return layouts;
- }
+ };
/**
- * @returns the transform that will correctly place the document decorations box.
+ * @returns the transform that will correctly place the document decorations box.
*/
private lookupIndividualTransform = (layout: Layout) => {
const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i));
const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll };
return this.props.ScreenToLocalTransform().translate(-pos.x, -pos.y);
- }
+ };
/**
* @returns the layout list converted from JSON
@@ -147,27 +173,32 @@ export class CollectionGridView extends CollectionSubView() {
this.props.Document.gridLayoutString = JSON.stringify(layouts);
}
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined);
/**
- *
- * @param layout
+ *
+ * @param layout
* @param dxf the x- and y-translations of the decorations box as a transform i.e. this.lookupIndividualTransform
- * @param width
- * @param height
+ * @param width
+ * @param height
* @returns the `ContentFittingDocumentView` of the node
*/
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
- return <DocumentView
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- Document={layout}
- DataDoc={layout.resolvedDataDoc as Doc}
- PanelWidth={width}
- PanelHeight={height}
- freezeDimensions={true}
- ScreenToLocalTransform={dxf}
- onClick={this.onChildClickHandler}
- renderDepth={this.props.renderDepth + 1}
- dontCenter={"y"}
- />;
+ return (
+ <DocumentView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+ Document={layout}
+ DataDoc={layout.resolvedDataDoc as Doc}
+ isContentActive={this.isChildContentActive}
+ PanelWidth={width}
+ PanelHeight={height}
+ ScreenToLocalTransform={dxf}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ onClick={this.onChildClickHandler}
+ renderDepth={this.props.renderDepth + 1}
+ dontCenter={this.props.Document.centerY ? '' : 'y'}
+ />
+ );
}
/**
@@ -177,7 +208,7 @@ export class CollectionGridView extends CollectionSubView() {
@action
setLayout = (layoutArray: Layout[]) => {
// for every child in the collection, check to see if there's a corresponding grid layout object and
- // updated layout object. If both exist, which they should, update the grid layout object from the updated object
+ // updated layout object. If both exist, which they should, update the grid layout object from the updated object
if (this.flexGrid) {
const savedLayouts = this.savedLayoutList;
this.childLayoutPairs.forEach(({ layout: doc }) => {
@@ -195,7 +226,7 @@ export class CollectionGridView extends CollectionSubView() {
undoBatch(() => this.setLayoutList(savedLayouts))();
}
}
- }
+ };
/**
* @returns a list of `ContentFittingDocumentView`s inside wrapper divs.
@@ -210,11 +241,12 @@ export class CollectionGridView extends CollectionSubView() {
const dxf = () => this.lookupIndividualTransform(l);
const width = () => (this.flexGrid ? l.w : this.defaultW) * this.colWidthPlusGap - this.margin;
const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.margin;
- child && collector.push(
- <div key={child.layout[Id]} className={"document-wrapper" + (this.flexGrid && this.props.isSelected() ? "" : " static")} >
- {this.getDisplayDoc(child.layout, dxf, width, height)}
- </div >
- );
+ child &&
+ collector.push(
+ <div key={child.layout[Id]} className={'document-wrapper' + (this.flexGrid && this.props.isSelected() ? '' : ' static')}>
+ {this.getDisplayDoc(child.layout, dxf, width, height)}
+ </div>
+ );
});
}
return collector;
@@ -224,14 +256,19 @@ export class CollectionGridView extends CollectionSubView() {
* @returns a list of `Layout` objects with attributes depending on whether the grid is flexible or static
*/
@computed get renderedLayoutList(): Layout[] {
- return this.flexGrid ?
- this.savedLayoutList.map(({ i, x, y, w, h }) => ({
- i, y, h,
- x: x + w > this.numCols ? 0 : x, // handles wrapping around of nodes when numCols decreases
- w: Math.min(w, this.numCols), // reduces width if greater than numCols
- static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout._lockedPosition, false) // checks if the lock position item has been selected in the context menu
- })) :
- this.savedLayoutList.map((layout, index) => { Object.assign(layout, this.unflexedPosition(index)); return layout; });
+ return this.flexGrid
+ ? this.savedLayoutList.map(({ i, x, y, w, h }) => ({
+ i,
+ y,
+ h,
+ x: x + w > this.numCols ? 0 : x, // handles wrapping around of nodes when numCols decreases
+ w: Math.min(w, this.numCols), // reduces width if greater than numCols
+ static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout._lockedPosition, false), // checks if the lock position item has been selected in the context menu
+ }))
+ : this.savedLayoutList.map((layout, index) => {
+ Object.assign(layout, this.unflexedPosition(index));
+ return layout;
+ });
}
/**
@@ -247,7 +284,7 @@ export class CollectionGridView extends CollectionSubView() {
return true;
}
return false;
- }
+ };
/**
* Handles external drop of images/PDFs etc from outside Dash.
@@ -256,7 +293,7 @@ export class CollectionGridView extends CollectionSubView() {
onExternalDrop = async (e: React.DragEvent): Promise<void> => {
this.dropLocation = this.screenToCell(e.clientX, e.clientY);
super.onExternalDrop(e, {});
- }
+ };
/**
* Handles the change in the value of the rowHeight slider.
@@ -264,64 +301,83 @@ export class CollectionGridView extends CollectionSubView() {
@action
onSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this._rowHeight = event.currentTarget.valueAsNumber;
- }
+ };
/**
* Handles the user clicking on the slider.
*/
@action
onSliderDown = (e: React.PointerEvent) => {
this._rowHeight = this.rowHeight; // uses _rowHeight during dragging and sets doc's rowHeight when finished so that operation is undoable
- setupMoveUpEvents(this, e, returnFalse, action(() => {
- undoBatch(() => this.props.Document.gridRowHeight = this._rowHeight)();
- this._rowHeight = undefined;
- }), emptyFunction, false, false);
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ action(() => {
+ undoBatch(() => (this.props.Document.gridRowHeight = this._rowHeight))();
+ this._rowHeight = undefined;
+ }),
+ emptyFunction,
+ false,
+ false
+ );
e.stopPropagation();
- }
+ };
/**
* Adds the display option to change the css display attribute of the `ContentFittingDocumentView`s
*/
onContextMenu = () => {
const displayOptionsMenu: ContextMenuProps[] = [];
- displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" });
- ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
- }
+ displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.props.Document.display = this.props.Document.display ? undefined : 'contents'), icon: 'copy' });
+ displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.props.Document.centerY = !this.props.Document.centerY), icon: 'copy' });
+ ContextMenu.Instance.addItem({ description: 'Display', subitems: displayOptionsMenu, icon: 'tv' });
+ };
/**
* Handles text document creation on double click.
*/
onPointerDown = (e: React.PointerEvent) => {
if (this.props.isContentActive(true)) {
- setupMoveUpEvents(this, e, returnFalse, returnFalse,
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
(e: PointerEvent, doubleTap?: boolean) => {
- if (doubleTap) {
- undoBatch(action(() => {
- const text = Docs.Create.TextDocument("", { _width: 150, _height: 50 });
- FormattedTextBox.SelectOnLoad = text[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
- Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
- this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
- }))();
+ if (doubleTap && !e.button) {
+ undoBatch(
+ action(() => {
+ const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
+ FormattedTextBox.SelectOnLoad = text[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
+ this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
+ })
+ )();
}
},
- false);
+ false
+ );
if (this.props.isSelected(true)) e.stopPropagation();
}
- }
+ };
render() {
return (
- <div className="collectionGridView-contents" ref={this.createDashEventsTarget}
- style={{ pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined }}
+ <div
+ className="collectionGridView-contents"
+ ref={this.createDashEventsTarget}
+ style={{ pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined }}
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
- onDrop={this.onExternalDrop}
- >
- <div className="collectionGridView-gridContainer" ref={this._containerRef}
- style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, "white") }}
+ onDrop={this.onExternalDrop}>
+ <div
+ className="collectionGridView-gridContainer"
+ ref={this._containerRef}
+ style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, 'white') }}
onWheel={e => e.stopPropagation()}
onScroll={action(e => {
if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
else this._scroll = e.currentTarget.scrollTop;
- })} >
+ })}>
<Grid
width={this.props.PanelWidth()}
nodeList={this.contents.length ? this.contents : null}
@@ -332,15 +388,21 @@ export class CollectionGridView extends CollectionSubView() {
setLayout={this.setLayout}
transformScale={this.props.ScreenToLocalTransform().Scale}
compactType={this.compaction} // determines whether nodes should remain in position, be bound to the top, or to the left
- preventCollision={BoolCast(this.props.Document.gridPreventCollision)}// determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
+ preventCollision={BoolCast(this.props.Document.gridPreventCollision)} // determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
margin={this.margin}
/>
- <input className="rowHeightSlider" type="range"
+ <input
+ className="rowHeightSlider"
+ type="range"
style={{ width: this.props.PanelHeight() - 30 }}
- min={1} value={this.rowHeight} max={this.props.PanelHeight() - 30}
- onPointerDown={this.onSliderDown} onChange={this.onSliderChange} />
+ min={1}
+ value={this.rowHeight}
+ max={this.props.PanelHeight() - 30}
+ onPointerDown={this.onSliderDown}
+ onChange={this.onSliderChange}
+ />
</div>
- </div >
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx
index 3d2ed0cf9..3d1d87aa0 100644
--- a/src/client/views/collections/collectionGrid/Grid.tsx
+++ b/src/client/views/collections/collectionGrid/Grid.tsx
@@ -1,14 +1,13 @@
import * as React from 'react';
-import { observer } from "mobx-react";
+import { observer } from 'mobx-react';
-import "../../../../../node_modules/react-grid-layout/css/styles.css";
-import "../../../../../node_modules/react-resizable/css/styles.css";
+import '../../../../../node_modules/react-grid-layout/css/styles.css';
+import '../../../../../node_modules/react-resizable/css/styles.css';
import * as GridLayout from 'react-grid-layout';
import { Layout } from 'react-grid-layout';
export { Layout } from 'react-grid-layout';
-
interface GridProps {
width: number;
nodeList: JSX.Element[] | null;
@@ -29,9 +28,10 @@ interface GridProps {
@observer
export default class Grid extends React.Component<GridProps> {
render() {
- const compactType = this.props.compactType === "vertical" || this.props.compactType === "horizontal" ? this.props.compactType : null;
+ const compactType = this.props.compactType === 'vertical' || this.props.compactType === 'horizontal' ? this.props.compactType : null;
return (
- <GridLayout className="layout"
+ <GridLayout
+ className="layout"
layout={this.props.layout}
cols={this.props.numCols}
rowHeight={this.props.rowHeight}
@@ -43,9 +43,8 @@ export default class Grid extends React.Component<GridProps> {
useCSSTransforms={true}
onLayoutChange={this.props.setLayout}
preventCollision={this.props.preventCollision}
- transformScale={1 / this.props.transformScale} // still doesn't work :(
- margin={[this.props.margin, this.props.margin]}
- >
+ transformScale={1 / this.props.transformScale} // still doesn't work :( ??
+ margin={[this.props.margin, this.props.margin]}>
{this.props.nodeList}
</GridLayout>
);
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 968048e39..e8df192cf 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -17,7 +17,7 @@
background-color: $medium-blue-alt;
}
- >input:not(:checked)~&.true {
+ > input:not(:checked) ~ &.true {
background-color: transparent;
}
@@ -31,7 +31,7 @@
overflow: visible !important;
}
- >span {
+ > span {
background: $dark-gray;
color: $white;
border-radius: 18px;
@@ -39,6 +39,10 @@
cursor: pointer;
}
+ .audio-title:hover {
+ text-decoration: underline;
+ }
+
.bottomPopup-background {
background: $medium-blue;
display: flex;
@@ -58,6 +62,7 @@
padding-right: 20px;
vertical-align: middle;
font-size: 12.5px;
+ pointer-events: all;
}
.bottomPopup-descriptions {
@@ -86,7 +91,7 @@
color: black;
}
- >label {
+ > label {
pointer-events: all;
cursor: pointer;
background-color: $medium-blue;
@@ -104,20 +109,20 @@
justify-content: center;
transition: 0.2s;
- &:hover{
+ &:hover {
filter: brightness(0.85);
}
}
- >input {
+ > input {
display: none;
}
- >input:not(:checked)~.collectionLinearView-content {
+ > input:not(:checked) ~ .collectionLinearView-content {
display: none;
}
- >input:checked~label {
+ > input:checked ~ label {
transform: rotate(45deg);
transition: transform 0.5s;
cursor: pointer;
@@ -151,4 +156,4 @@
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 44762dbe3..0d7d67dd8 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -7,6 +7,8 @@ import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { Colors, Shadows } from '../../global/globalEnums';
@@ -14,15 +16,14 @@ import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
import { DocumentView } from '../../nodes/DocumentView';
import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
import { StyleProp } from '../../StyleProvider';
+import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
import { CollectionSubView } from '../CollectionSubView';
-import { CollectionViewType } from '../CollectionView';
-import "./CollectionLinearView.scss";
-
+import './CollectionLinearView.scss';
/**
- * CollectionLinearView is the class for rendering the horizontal collection
+ * CollectionLinearView is the class for rendering the horizontal collection
* of documents, it useful for horizontal menus. It can either be expandable
- * or not using the linearViewExpandable field.
+ * or not using the linearViewExpandable field.
* It is used in the following locations:
* - It is used in the popup menu on the bottom left (see docButtons() in MainView.tsx)
* - It is used for the context sensitive toolbar at the top (see contMenuButtons() in CollectionMenu.tsx)
@@ -43,45 +44,47 @@ export class CollectionLinearView extends CollectionSubView() {
}
componentDidMount() {
- this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10),
+ this._widthDisposer = reaction(
+ () => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10),
width => this.childDocs.length && (this.layoutDoc._width = width),
{ fireImmediately: true }
);
this._selectedDisposer = reaction(
() => NumCast(this.layoutDoc.selectedIndex),
- (i) => runInAction(() => {
- this._selectedIndex = i;
- let selected: any = undefined;
- this.childLayoutPairs.map(async (pair, ind) => {
- const isSelected = this._selectedIndex === ind;
- if (isSelected) {
- selected = pair;
- }
- else {
- ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
+ i =>
+ runInAction(() => {
+ this._selectedIndex = i;
+ let selected: any = undefined;
+ this.childLayoutPairs.map(async (pair, ind) => {
+ const isSelected = this._selectedIndex === ind;
+ if (isSelected) {
+ selected = pair;
+ } else {
+ ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
+ }
+ });
+ if (selected && selected.layout) {
+ ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
}
- });
- if (selected && selected.layout) {
- ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
- }
- }),
+ }),
{ fireImmediately: true }
);
}
- protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view
+ protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+ //used for stacking and masonry view
this._dropDisposer && this._dropDisposer();
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
- }
+ };
dimension = () => NumCast(this.rootDoc._height); // 2 * the padding
getTransform = (ele: Opt<HTMLDivElement>) => {
if (!ele) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
return new Transform(-translateX, -translateY, 1);
- }
+ };
@action
exitLongLinks = () => {
@@ -94,73 +97,79 @@ export class CollectionLinearView extends CollectionSubView() {
}
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
- }
+ };
@action
changeDescriptionSetting = () => {
if (LinkDescriptionPopup.showDescriptions) {
- if (LinkDescriptionPopup.showDescriptions === "ON") {
- LinkDescriptionPopup.showDescriptions = "OFF";
+ if (LinkDescriptionPopup.showDescriptions === 'ON') {
+ LinkDescriptionPopup.showDescriptions = 'OFF';
LinkDescriptionPopup.descriptionPopup = false;
} else {
- LinkDescriptionPopup.showDescriptions = "ON";
+ LinkDescriptionPopup.showDescriptions = 'ON';
}
} else {
- LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.showDescriptions = 'OFF';
LinkDescriptionPopup.descriptionPopup = false;
}
- }
+ };
myContextMenu = (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
- }
-
+ };
- getDisplayDoc = (doc: Doc) => {
+ getDisplayDoc = (doc: Doc, preview: boolean = false) => {
const nested = doc._viewType === CollectionViewType.Linear;
const hidden = doc.hidden === true;
let dref: Opt<HTMLDivElement>;
const docXf = () => this.getTransform(dref);
// const scalable = pair.layout.onClick || pair.layout.onDragStart;
- return hidden ? (null) : <div className={`collectionLinearView-docBtn`} key={doc[Id]} ref={r => dref = r || undefined}
- style={{
- pointerEvents: "all",
- width: nested ? undefined : NumCast(doc._width),
- height: nested ? undefined : NumCast(doc._height),
- marginLeft: !nested ? 2.5 : 0,
- marginRight: !nested ? 2.5 : 0,
- // width: NumCast(pair.layout._width),
- // height: NumCast(pair.layout._height),
- }} >
- <DocumentView
- Document={doc}
- isContentActive={this.props.isContentActive}
- isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={emptyFunction}
- rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
- ScreenToLocalTransform={docXf}
- PanelWidth={nested ? doc[WidthSym] : this.dimension}
- PanelHeight={nested ? doc[HeightSym] : this.dimension}
- renderDepth={this.props.renderDepth + 1}
- focus={emptyFunction}
- styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={returnEmptyDoclist}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={this.props.docFilters}
- docRangeFilters={this.props.docRangeFilters}
- searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- </div>;
- }
+ return hidden ? null : (
+ <div
+ className={preview ? 'preview' : `collectionLinearView-docBtn`}
+ key={doc[Id]}
+ ref={r => (dref = r || undefined)}
+ style={{
+ pointerEvents: 'all',
+ width: nested ? undefined : NumCast(doc._width),
+ height: nested ? undefined : NumCast(doc._height),
+ marginLeft: !nested ? 2.5 : 0,
+ marginRight: !nested ? 2.5 : 0,
+ // width: NumCast(pair.layout._width),
+ // height: NumCast(pair.layout._height),
+ }}>
+ <DocumentView
+ Document={doc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={docXf}
+ PanelWidth={nested ? doc[WidthSym] : this.dimension}
+ PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension}
+ renderDepth={this.props.renderDepth + 1}
+ dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ hideResizeHandles={true}
+ />
+ </div>
+ );
+ };
render() {
const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label
@@ -172,57 +181,99 @@ export class CollectionLinearView extends CollectionSubView() {
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle
- const menuOpener = <label htmlFor={`${guid}`}
- style={{
- boxShadow: floating ? Shadows.STANDARD_SHADOW : undefined,
- color: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.BLACK,
- backgroundColor: backgroundColor === color ? "black" : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY
- }}
- onPointerDown={e => e.stopPropagation()} >
- <div className="collectionLinearView-menuOpener">
- {BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : <FontAwesomeIcon icon={"minus"} /> : icon ? icon : <FontAwesomeIcon icon={"plus"} />}
- </div>
- </label>;
-
- return <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : "transparent" }}>
- <div className="collectionLinearView" ref={this.createDashEventsTarget}
- onContextMenu={this.myContextMenu} >
- {!expandable ? (null) : <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close" : "Open"}</div></>} placement="top">
- {menuOpener}
- </Tooltip>}
- <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
- onChange={action(() => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
-
- <div className="collectionLinearView-content"
- style={{
- height: this.dimension(),
- flexDirection: flexDir,
- gap: flexGap
- }}>
- {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
- </div>
- {DocumentLinksButton.StartLink && StrCast(this.layoutDoc.title) === "docked buttons" ? <span className="bottomPopup-background" style={{
- pointerEvents: "all"
+ const menuOpener = (
+ <label
+ htmlFor={`${guid}`}
+ style={{
+ boxShadow: floating ? Shadows.STANDARD_SHADOW : undefined,
+ color: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.BLACK,
+ backgroundColor: backgroundColor === color ? 'black' : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY,
}}
- onPointerDown={e => e.stopPropagation()} >
- <span className="bottomPopup-text" >
- Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b>
- </span>
-
- <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top">
- <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
- Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
- </span>
- </Tooltip>
+ onPointerDown={e => e.stopPropagation()}>
+ <div className="collectionLinearView-menuOpener">{BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : <FontAwesomeIcon icon={'minus'} /> : icon ? icon : <FontAwesomeIcon icon={'plus'} />}</div>
+ </label>
+ );
- <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top">
- <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
- Stop
- </span>
- </Tooltip>
+ return (
+ <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : 'transparent' }}>
+ <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu}>
+ {!expandable ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? 'Close' : 'Open'}</div>
+ </>
+ }
+ placement="top">
+ {menuOpener}
+ </Tooltip>
+ )}
+ <input
+ id={`${guid}`}
+ type="checkbox"
+ checked={BoolCast(this.props.Document.linearViewIsExpanded)}
+ ref={this.addMenuToggle}
+ onChange={action(() => (this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked))}
+ />
+
+ <div
+ className="collectionLinearView-content"
+ style={{
+ height: this.dimension(),
+ flexDirection: flexDir,
+ gap: flexGap,
+ }}>
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+ </div>
+ {!DocumentLinksButton.StartLink || this.layoutDoc !== Doc.MyDockedBtns ? null : (
+ <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
+ <span className="bottomPopup-text">
+ Creating link from:{' '}
+ <b>
+ {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') +
+ (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')}
+ </b>
+ </span>
- </span> : null}
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Toggle description pop-up'} </div>
+ </>
+ }
+ placement="top">
+ <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
+ Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'}
+ </span>
+ </Tooltip>
+
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">Exit linking mode</div>
+ </>
+ }
+ placement="top">
+ <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
+ Stop
+ </span>
+ </Tooltip>
+ </span>
+ )}
+ {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== Doc.MyDockedBtns ? null : (
+ <span className="bottomPopup-background">
+ <span className="bottomPopup-text">
+ Currently playing:
+ {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => (
+ <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}>
+ {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')}
+ </span>
+ ))}
+ </span>
+ </span>
+ )}
+ </div>
</div>
- </div>;
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 2bdf92417..465dbfe6d 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,20 +1,20 @@
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
-import * as React from "react";
-import { Doc } from '../../../../fields/Doc';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse } from '../../../../Utils';
+import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
import { CollectionSubView } from '../CollectionSubView';
-import "./CollectionMulticolumnView.scss";
+import './CollectionMulticolumnView.scss';
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
-
interface WidthSpecifier {
magnitude: number;
unit: string;
@@ -26,8 +26,8 @@ interface LayoutData {
}
export const DimUnit = {
- Pixel: "px",
- Ratio: "*"
+ Pixel: 'px',
+ Ratio: '*',
};
const resolvedUnits = Object.values(DimUnit);
@@ -35,14 +35,13 @@ const resizerWidth = 8;
@observer
export class CollectionMulticolumnView extends CollectionSubView() {
-
/**
* @returns the list of layout documents whose width unit is
* *, denoting that it will be displayed with a ratio, not fixed pixel, value
*/
@computed
private get ratioDefinedDocs() {
- return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio);
+ return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
}
@computed
@@ -64,10 +63,10 @@ export class CollectionMulticolumnView extends CollectionSubView() {
let starSum = 0;
const widthSpecifiers: WidthSpecifier[] = [];
this.childLayoutPairs.map(pair => {
- const unit = StrCast(pair.layout._dimUnit, "*");
+ const unit = StrCast(pair.layout._dimUnit, '*');
const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
- (unit === DimUnit.Ratio) && (starSum += magnitude);
+ unit === DimUnit.Ratio && (starSum += magnitude);
widthSpecifiers.push({ magnitude, unit });
}
/**
@@ -99,14 +98,13 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* This returns the total quantity, in pixels, that this
* view needs to reserve for child documents that have
* (with higher priority) requested a fixed pixel width.
- *
+ *
* If the underlying resolvedLayoutInformation returns null
* because we're waiting on promises to resolve, this value will be undefined as well.
*/
@computed
private get totalFixedAllocation(): number | undefined {
- return this.resolvedLayoutInformation?.widthSpecifiers.reduce(
- (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
+ return this.resolvedLayoutInformation?.widthSpecifiers.reduce((sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
}
/**
@@ -114,7 +112,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* view needs to reserve for child documents that have
* (with lower priority) requested a certain relative proportion of the
* remaining pixel width not allocated for fixed widths.
- *
+ *
* If the underlying totalFixedAllocation returns undefined
* because we're waiting indirectly on promises to resolve, this value will be undefined as well.
*/
@@ -134,7 +132,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
* this accessor returns 1000 / (2 + 2 + 1), or 200px.
* Elsewhere, this is then multiplied by each relative-width
* document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
- *
+ *
* If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
* because we're waiting indirectly on promises to resolve, this value will be undefined as well.
*/
@@ -164,17 +162,17 @@ export class CollectionMulticolumnView extends CollectionSubView() {
return 0; // we're still waiting on promises to resolve
}
let width = NumCast(layout._dimMagnitude, this.minimumDim);
- if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) {
+ if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
width *= columnUnitLength;
}
return width;
- }
+ };
/**
* @returns the transform that will correctly place
* the document decorations box, shifted to the right by
* the sum of all the resolved column widths of the
- * documents before the target.
+ * documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
const columnUnitLength = this.columnUnitLength;
@@ -184,73 +182,111 @@ export class CollectionMulticolumnView extends CollectionSubView() {
let offset = 0;
for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
- return this.props.ScreenToLocalTransform().translate(-offset / (this.props.scaling?.() || 1), 0);
+ return this.props.ScreenToLocalTransform().translate(-offset / (this.props.NativeDimScaling?.() || 1), 0);
}
offset += this.lookupPixels(candidate) + resizerWidth;
}
return Transform.Identity(); // type coersion, this case should never be hit
- }
+ };
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (super.onInternalDrop(e, de)) {
- de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
- d._dimUnit = "*";
- d._dimMagnitude = 1;
- }));
+ let dropInd = -1;
+ if (de.complete.docDragData && this._mainCont) {
+ let curInd = -1;
+ de.complete.docDragData?.droppedDocuments.forEach(
+ action((d: Doc) => {
+ curInd = this.childDocs.indexOf(d);
+ })
+ );
+ Array.from(this._mainCont.children).forEach((child, index) => {
+ const brect = child.getBoundingClientRect();
+ if (brect.x < de.x && brect.x + brect.width > de.x) {
+ if (curInd !== -1 && curInd === Math.floor(index / 2)) {
+ dropInd = curInd;
+ } else if (child.className === 'multiColumnResizer') {
+ dropInd = Math.floor(index / 2);
+ } else {
+ dropInd = Math.ceil(index / 2 + (de.x - brect.x > brect.width / 2 ? 0 : -1));
+ }
+ }
+ });
+ if (super.onInternalDrop(e, de)) {
+ de.complete.docDragData?.droppedDocuments.forEach(
+ action((d: Doc) => {
+ d._dimUnit = '*';
+ d._dimMagnitude = 1;
+ if (dropInd !== curInd || dropInd === -1) {
+ if (this.childDocs.includes(d)) {
+ if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+ }
+ Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
+ Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+ }
+ })
+ );
+ }
}
return false;
- }
-
+ };
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
- if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
return true;
}
return this.props.addDocTab(doc, where);
- }
- getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
- return <DocumentView
- Document={layout}
- DataDoc={layout.resolvedDataDoc as Doc}
- styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={this.props.docViewPath}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- freezeDimensions={this.props.childFreezeDimensions}
- renderDepth={this.props.renderDepth + 1}
- isContentActive={emptyFunction}
- PanelWidth={width}
- PanelHeight={height}
- rootSelected={this.rootSelected}
- dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
- onClick={this.onChildClickHandler}
- onDoubleClick={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={dxf}
- focus={this.props.focus}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
- />;
- }
+ };
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isChildContentActive = () =>
+ ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
+ getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
+ return (
+ <DocumentView
+ Document={layout}
+ DataDoc={layout.resolvedDataDoc as Doc}
+ styleProvider={this.props.styleProvider}
+ docViewPath={this.props.docViewPath}
+ LayoutTemplate={this.props.childLayoutTemplate}
+ LayoutTemplateString={this.props.childLayoutString}
+ renderDepth={this.props.renderDepth + 1}
+ PanelWidth={width}
+ PanelHeight={height}
+ rootSelected={this.rootSelected}
+ dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+ onClick={this.onChildClickHandler}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ suppressSetHeight={true}
+ ScreenToLocalTransform={dxf}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={this.props.childHideResizeHandles?.()}
+ hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+ fitContentsToBox={this.props.fitContentsToBox}
+ focus={this.props.focus}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ dontRegisterView={this.props.dontRegisterView}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ />
+ );
+ };
/**
* @returns the resolved list of rendered child documents, displayed
- * at their resolved pixel widths, each separated by a resizer.
+ * at their resolved pixel widths, each separated by a resizer.
*/
@computed
private get contents(): JSX.Element[] | null {
@@ -259,22 +295,20 @@ export class CollectionMulticolumnView extends CollectionSubView() {
const collector: JSX.Element[] = [];
for (let i = 0; i < childLayoutPairs.length; i++) {
const { layout } = childLayoutPairs[i];
- const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1));
+ const dxf = () =>
+ this.lookupIndividualTransform(layout)
+ .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))
+ .scale(this.props.NativeDimScaling?.() || 1);
const width = () => this.lookupPixels(layout);
const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
collector.push(
- <div className={"document-wrapper"}
- key={"wrapper" + i}
- style={{ width: width() }} >
+ <div className={'document-wrapper'} key={'wrapper' + i} style={{ width: width() }}>
{this.getDisplayDoc(layout, dxf, width, height)}
- <WidthLabel
- layout={layout}
- collectionDoc={Document}
- />
+ <WidthLabel layout={layout} collectionDoc={Document} />
</div>,
<ResizeBar
width={resizerWidth}
- key={"resizer" + i}
+ key={'resizer' + i}
styleProvider={this.props.styleProvider}
isContentActive={this.props.isContentActive}
select={this.props.select}
@@ -290,16 +324,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
render(): JSX.Element {
return (
- <div className={"collectionMulticolumnView_contents"}
+ <div
+ className={'collectionMulticolumnView_contents'}
+ ref={this.createDashEventsTarget}
style={{
width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
- marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
- marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
- }} ref={this.createDashEventsTarget}>
+ marginLeft: NumCast(this.props.Document._xMargin),
+ marginRight: NumCast(this.props.Document._xMargin),
+ marginTop: NumCast(this.props.Document._yMargin),
+ marginBottom: NumCast(this.props.Document._yMargin),
+ }}>
{this.contents}
</div>
);
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 7e2b83230..f8de4e5de 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,16 +1,17 @@
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
-import * as React from "react";
-import { Doc } from '../../../../fields/Doc';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse } from '../../../../Utils';
+import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
import { CollectionSubView } from '../CollectionSubView';
-import "./CollectionMultirowView.scss";
+import './CollectionMultirowView.scss';
import HeightLabel from './MultirowHeightLabel';
import ResizeBar from './MultirowResizer';
@@ -25,8 +26,8 @@ interface LayoutData {
}
export const DimUnit = {
- Pixel: "px",
- Ratio: "*"
+ Pixel: 'px',
+ Ratio: '*',
};
const resolvedUnits = Object.values(DimUnit);
@@ -34,14 +35,13 @@ const resizerHeight = 8;
@observer
export class CollectionMultirowView extends CollectionSubView() {
-
/**
* @returns the list of layout documents whose width unit is
* *, denoting that it will be displayed with a ratio, not fixed pixel, value
*/
@computed
private get ratioDefinedDocs() {
- return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio);
+ return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
}
@computed
@@ -63,10 +63,10 @@ export class CollectionMultirowView extends CollectionSubView() {
let starSum = 0;
const heightSpecifiers: HeightSpecifier[] = [];
this.childLayoutPairs.map(pair => {
- const unit = StrCast(pair.layout._dimUnit, "*");
+ const unit = StrCast(pair.layout._dimUnit, '*');
const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
- (unit === DimUnit.Ratio) && (starSum += magnitude);
+ unit === DimUnit.Ratio && (starSum += magnitude);
heightSpecifiers.push({ magnitude, unit });
}
/**
@@ -98,14 +98,13 @@ export class CollectionMultirowView extends CollectionSubView() {
* This returns the total quantity, in pixels, that this
* view needs to reserve for child documents that have
* (with higher priority) requested a fixed pixel width.
- *
+ *
* If the underlying resolvedLayoutInformation returns null
* because we're waiting on promises to resolve, this value will be undefined as well.
*/
@computed
private get totalFixedAllocation(): number | undefined {
- return this.resolvedLayoutInformation?.heightSpecifiers.reduce(
- (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
+ return this.resolvedLayoutInformation?.heightSpecifiers.reduce((sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
}
/**
@@ -113,7 +112,7 @@ export class CollectionMultirowView extends CollectionSubView() {
* view needs to reserve for child documents that have
* (with lower priority) requested a certain relative proportion of the
* remaining pixel width not allocated for fixed widths.
- *
+ *
* If the underlying totalFixedAllocation returns undefined
* because we're waiting indirectly on promises to resolve, this value will be undefined as well.
*/
@@ -133,7 +132,7 @@ export class CollectionMultirowView extends CollectionSubView() {
* this accessor returns 1000 / (2 + 2 + 1), or 200px.
* Elsewhere, this is then multiplied by each relative-width
* document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
- *
+ *
* If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
* because we're waiting indirectly on promises to resolve, this value will be undefined as well.
*/
@@ -163,17 +162,17 @@ export class CollectionMultirowView extends CollectionSubView() {
return 0; // we're still waiting on promises to resolve
}
let height = NumCast(layout._dimMagnitude, this.minimumDim);
- if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) {
+ if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
height *= rowUnitLength;
}
return height;
- }
+ };
/**
* @returns the transform that will correctly place
* the document decorations box, shifted to the right by
* the sum of all the resolved row widths of the
- * documents before the target.
+ * documents before the target.
*/
private lookupIndividualTransform = (layout: Doc) => {
const rowUnitLength = this.rowUnitLength;
@@ -183,73 +182,110 @@ export class CollectionMultirowView extends CollectionSubView() {
let offset = 0;
for (const { layout: candidate } of this.childLayoutPairs) {
if (candidate === layout) {
- return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.scaling?.() || 1));
+ return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.NativeDimScaling?.() || 1));
}
offset += this.lookupPixels(candidate) + resizerHeight;
}
return Transform.Identity(); // type coersion, this case should never be hit
- }
+ };
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (super.onInternalDrop(e, de)) {
- de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
- d._dimUnit = "*";
- d._dimMagnitude = 1;
- }));
+ let dropInd = -1;
+ if (de.complete.docDragData && this._mainCont) {
+ let curInd = -1;
+ de.complete.docDragData?.droppedDocuments.forEach(
+ action((d: Doc) => {
+ curInd = this.childDocs.indexOf(d);
+ })
+ );
+ Array.from(this._mainCont.children).forEach((child, index) => {
+ const brect = child.getBoundingClientRect();
+ if (brect.y < de.y && brect.y + brect.height > de.y) {
+ if (curInd !== -1 && curInd === Math.floor(index / 2)) {
+ dropInd = curInd;
+ } else if (child.className === 'multiColumnResizer') {
+ dropInd = Math.floor(index / 2);
+ } else {
+ dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1));
+ }
+ }
+ });
+ if (super.onInternalDrop(e, de)) {
+ de.complete.docDragData?.droppedDocuments.forEach(
+ action((d: Doc) => {
+ d._dimUnit = '*';
+ d._dimMagnitude = 1;
+ if (dropInd !== curInd || dropInd === -1) {
+ if (this.childDocs.includes(d)) {
+ if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+ }
+ Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
+ Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+ }
+ })
+ );
+ }
}
return false;
- }
-
+ };
onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
- if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
return true;
}
return this.props.addDocTab(doc, where);
- }
- getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
- return <DocumentView
- Document={layout}
- DataDoc={layout.resolvedDataDoc as Doc}
- styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={this.props.docViewPath}
- LayoutTemplate={this.props.childLayoutTemplate}
- LayoutTemplateString={this.props.childLayoutString}
- freezeDimensions={this.props.childFreezeDimensions}
- renderDepth={this.props.renderDepth + 1}
- PanelWidth={width}
- PanelHeight={height}
- rootSelected={this.rootSelected}
- dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
- onClick={this.onChildClickHandler}
- onDoubleClick={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={dxf}
- focus={this.props.focus}
- docFilters={this.childDocFilters}
- isContentActive={emptyFunction}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
- />;
- }
+ };
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isChildContentActive = () =>
+ ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
+ getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
+ return (
+ <DocumentView
+ Document={layout}
+ DataDoc={layout.resolvedDataDoc as Doc}
+ styleProvider={this.props.styleProvider}
+ docViewPath={this.props.docViewPath}
+ LayoutTemplate={this.props.childLayoutTemplate}
+ LayoutTemplateString={this.props.childLayoutString}
+ renderDepth={this.props.renderDepth + 1}
+ PanelWidth={width}
+ PanelHeight={height}
+ rootSelected={this.rootSelected}
+ dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+ onClick={this.onChildClickHandler}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ ScreenToLocalTransform={dxf}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={this.props.childHideResizeHandles?.()}
+ hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+ fitContentsToBox={this.props.fitContentsToBox}
+ focus={this.props.focus}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ dontRegisterView={this.props.dontRegisterView}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ />
+ );
+ };
/**
* @returns the resolved list of rendered child documents, displayed
- * at their resolved pixel widths, each separated by a resizer.
+ * at their resolved pixel widths, each separated by a resizer.
*/
@computed
private get contents(): JSX.Element[] | null {
@@ -258,13 +294,14 @@ export class CollectionMultirowView extends CollectionSubView() {
const collector: JSX.Element[] = [];
for (let i = 0; i < childLayoutPairs.length; i++) {
const { layout } = childLayoutPairs[i];
- const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1));
+ const dxf = () =>
+ this.lookupIndividualTransform(layout)
+ .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))
+ .scale(this.props.NativeDimScaling?.() || 1);
const height = () => this.lookupPixels(layout);
const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
collector.push(
- <div className={"document-wrapper"}
- style={{ height: height() }}
- key={"wrapper" + i} >
+ <div className={'document-wrapper'} style={{ height: height() }} key={'wrapper' + i}>
{this.getDisplayDoc(layout, dxf, width, height)}
<HeightLabel layout={layout} collectionDoc={Document} />
</div>,
@@ -272,7 +309,7 @@ export class CollectionMultirowView extends CollectionSubView() {
height={resizerHeight}
styleProvider={this.props.styleProvider}
isContentActive={this.props.isContentActive}
- key={"resizer" + i}
+ key={'resizer' + i}
columnUnitLength={this.getRowUnitLength}
toTop={layout}
toBottom={childLayoutPairs[i + 1]?.layout}
@@ -285,16 +322,19 @@ export class CollectionMultirowView extends CollectionSubView() {
render(): JSX.Element {
return (
- <div className={"collectionMultirowView_contents"}
+ <div
+ className={'collectionMultirowView_contents'}
style={{
width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
- marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
- marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
- }} ref={this.createDashEventsTarget}>
+ marginLeft: NumCast(this.props.Document._xMargin),
+ marginRight: NumCast(this.props.Document._xMargin),
+ marginTop: NumCast(this.props.Document._yMargin),
+ marginBottom: NumCast(this.props.Document._yMargin),
+ }}
+ ref={this.createDashEventsTarget}>
{this.contents}
</div>
);
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index c2bb3b3ac..adcd9e1e3 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -199,7 +199,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
// Jump to the this document
- DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext,
+ DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [],
undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc));
}
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index dc35b5749..9653f2808 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -278,6 +278,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
@undoBatch
onKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === "Enter") {
+ e.stopPropagation();
if (this._searchTerm.includes(":")) {
const colpos = this._searchTerm.indexOf(":");
const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
@@ -320,7 +321,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"];
const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
const showKeys = new Set<string>();
- [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.UserDoc().noviceMode ||
+ [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.noviceMode ||
whitelistKeys.includes(key)
|| ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null);
return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm));
@@ -490,7 +491,9 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
<div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}>
<input className="keys-search" style={{ width: "100%" }}
- ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown}
+ ref={this._inputRef} type="text"
+ value={this._searchTerm} placeholder="Column key"
+ onKeyDown={this.onKeyDown}
onChange={e => this.onChange(e.target.value)}
onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }}
onFocus={this.onFocus} ></input>
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
index 2df95ffd8..28d2e6ab1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
@@ -1,20 +1,13 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action } from "mobx";
-import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table";
-import { Doc } from "../../../../fields/Doc";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { Cast, FieldValue, StrCast } from "../../../../fields/Types";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { ContextMenu } from "../../ContextMenu";
-import "./CollectionSchemaView.scss";
+import React = require('react');
+import { action } from 'mobx';
+import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import './CollectionSchemaView.scss';
export interface MovableColumnProps {
- columnRenderer: TableCellRenderer;
+ columnRenderer: React.ReactNode;
columnValue: SchemaHeaderField;
allColumns: SchemaHeaderField[];
reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void;
@@ -26,7 +19,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
// The container of the function that is responsible for moving the column over to a new plac
private _colDropDisposer?: DragManager.DragDropDisposer;
// initial column position
- private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
+ private _startDragPosition: { x: number; y: number } = { x: 0, y: 0 };
// sensitivity to being dragged, in pixels
private _sensitivity: number = 16;
// Column reference ID
@@ -35,45 +28,45 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
onPointerEnter = (e: React.PointerEvent): void => {
// if the column is left-clicked and it is being dragged
if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = "collectionSchema-col-wrapper";
- document.addEventListener("pointermove", this.onDragMove, true);
+ this._header!.current!.className = 'collectionSchema-col-wrapper';
+ document.addEventListener('pointermove', this.onDragMove, true);
}
- }
+ };
onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = "collectionSchema-col-wrapper";
- document.removeEventListener("pointermove", this.onDragMove, true);
- !e.buttons && document.removeEventListener("pointermove", this.onPointerMove);
- }
+ this._header!.current!.className = 'collectionSchema-col-wrapper';
+ document.removeEventListener('pointermove', this.onDragMove, true);
+ !e.buttons && document.removeEventListener('pointermove', this.onPointerMove);
+ };
onDragMove = (e: PointerEvent): void => {
// only take into account the horizonal direction when a column is dragged
const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
const rect = this._header!.current!.getBoundingClientRect();
// Now store the point at the top center of the column when it was in its original position
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
// to be compared with its new horizontal position
const before = x[0] < bounds[0];
- this._header!.current!.className = "collectionSchema-col-wrapper";
- if (before) this._header!.current!.className += " col-before";
- if (!before) this._header!.current!.className += " col-after";
+ this._header!.current!.className = 'collectionSchema-col-wrapper';
+ if (before) this._header!.current!.className += ' col-before';
+ if (!before) this._header!.current!.className += ' col-after';
e.stopPropagation();
- }
+ };
createColDropTarget = (ele: HTMLDivElement) => {
this._colDropDisposer?.();
if (ele) {
this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
}
- }
+ };
colDrop = (e: Event, de: DragManager.DropEvent) => {
- document.removeEventListener("pointermove", this.onDragMove, true);
+ document.removeEventListener('pointermove', this.onDragMove, true);
// we only care about whether the column is shifted to the side
const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
// get the dimensions of the smallest rectangle that bounds the header
const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top);
// get whether the column was dragged before or after where it is now
const before = x[0] < bounds[0];
const colDragData = de.complete.columnDragData;
@@ -84,20 +77,20 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
return true;
}
return false;
- }
+ };
onPointerMove = (e: PointerEvent) => {
const onRowMove = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
const dragData = new DragManager.ColumnDragData(this.props.columnValue);
DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y);
};
const onRowUp = (): void => {
- document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
};
// if the left mouse button is the one being held
@@ -105,30 +98,29 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
// If the movemnt of the drag exceeds the sensitivity value
if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
- document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener('pointermove', this.onPointerMove);
e.stopPropagation();
- document.addEventListener("pointermove", onRowMove);
- document.addEventListener("pointerup", onRowUp);
+ document.addEventListener('pointermove', onRowMove);
+ document.addEventListener('pointerup', onRowUp);
}
}
- }
+ };
onPointerUp = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ };
@action
onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
this._dragRef = ref;
const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
// If the cell thing dragged is not being edited
- if (!(e.target as any)?.tagName.includes("INPUT")) {
+ if (!(e.target as any)?.tagName.includes('INPUT')) {
this._startDragPosition = { x: dx, y: dy };
- document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener('pointermove', this.onPointerMove);
}
- }
-
+ };
render() {
const reference = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
index 0e19ef3d9..f872637e5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
@@ -1,17 +1,16 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action } from "mobx";
-import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table";
-import { Doc } from "../../../../fields/Doc";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { Cast, FieldValue, StrCast } from "../../../../fields/Types";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { ContextMenu } from "../../ContextMenu";
-import "./CollectionSchemaView.scss";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action } from 'mobx';
+import * as React from 'react';
+import { ReactTableDefaults, RowInfo } from 'react-table';
+import { Doc } from '../../../../fields/Doc';
+import { Cast, FieldValue, StrCast } from '../../../../fields/Types';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
+import './CollectionSchemaView.scss';
export interface MovableRowProps {
rowInfo: RowInfo;
@@ -25,7 +24,7 @@ export interface MovableRowProps {
addDocTab: any;
}
-export class MovableRow extends React.Component<MovableRowProps> {
+export class MovableRow extends React.Component<React.PropsWithChildren<MovableRowProps>> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _rowDropDisposer?: DragManager.DragDropDisposer;
@@ -33,28 +32,27 @@ export class MovableRow extends React.Component<MovableRowProps> {
// Create one when the mouse starts hovering...
onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = "collectionSchema-row-wrapper";
- document.addEventListener("pointermove", this.onDragMove, true);
+ this._header!.current!.className = 'collectionSchema-row-wrapper';
+ document.addEventListener('pointermove', this.onDragMove, true);
}
- }
+ };
// ... and delete it when the mouse leaves
onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = "collectionSchema-row-wrapper";
- document.removeEventListener("pointermove", this.onDragMove, true);
- }
+ this._header!.current!.className = 'collectionSchema-row-wrapper';
+ document.removeEventListener('pointermove', this.onDragMove, true);
+ };
// The method for the event listener, reorders columns when dragged to their new locations.
onDragMove = (e: PointerEvent): void => {
const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
const rect = this._header!.current!.getBoundingClientRect();
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
const before = x[1] < bounds[1];
- this._header!.current!.className = "collectionSchema-row-wrapper";
- if (before) this._header!.current!.className += " row-above";
- if (!before) this._header!.current!.className += " row-below";
+ this._header!.current!.className = 'collectionSchema-row-wrapper';
+ if (before) this._header!.current!.className += ' row-above';
+ if (!before) this._header!.current!.className += ' row-below';
e.stopPropagation();
- }
+ };
componentWillUnmount() {
-
this._rowDropDisposer?.();
}
//
@@ -63,7 +61,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
if (ele) {
this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
- }
+ };
// Controls what hppens when a row is dragged and dropped
rowDrop = (e: Event, de: DragManager.DropEvent) => {
this.onPointerLeave(e as any);
@@ -81,34 +79,34 @@ export class MovableRow extends React.Component<MovableRowProps> {
if (docDragData.draggedDocuments[0] === rowDoc) return true;
const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before);
const movedDocs = docDragData.draggedDocuments;
- return (docDragData.dropAction || docDragData.userDropAction) ?
- docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
- : (docDragData.moveDocument) ?
- movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
- : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
+ return docDragData.dropAction || docDragData.userDropAction
+ ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
+ : docDragData.moveDocument
+ ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
+ : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
}
return false;
- }
+ };
onRowContextMenu = (e: React.MouseEvent): void => {
- const description = this.props.rowWrapped ? "Unwrap text on row" : "Text wrap row";
- ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" });
- }
+ const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row';
+ ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' });
+ };
@undoBatch
@action
move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
- }
+ };
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- console.log("yes");
- if (e.key === "Backspace" || e.key === "Delete") {
+ console.log('yes');
+ if (e.key === 'Backspace' || e.key === 'Delete') {
undoBatch(() => this.props.removeDoc(this.props.rowInfo.original));
}
- }
+ };
render() {
const { children = null, rowInfo } = this.props;
@@ -120,23 +118,29 @@ export class MovableRow extends React.Component<MovableRowProps> {
const { original } = rowInfo;
const doc = FieldValue(Cast(original, Doc));
- if (!doc) return (null);
+ if (!doc) return null;
const reference = React.createRef<HTMLDivElement>();
const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType);
- let className = "collectionSchema-row";
- if (this.props.rowFocused) className += " row-focused";
- if (this.props.rowWrapped) className += " row-wrapped";
+ let className = 'collectionSchema-row';
+ if (this.props.rowFocused) className += ' row-focused';
+ if (this.props.rowWrapped) className += ' row-wrapped';
return (
<div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
<div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} >
+ <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown}>
<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, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
+ <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, 'add:right')}>
+ <FontAwesomeIcon icon="external-link-alt" size="sm" />
+ </div>
</div>
{children}
</ReactTableDefaults.TrComponent>
@@ -144,4 +148,4 @@ export class MovableRow extends React.Component<MovableRowProps> {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index b64e9dac1..19401c7f0 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,4 +1,5 @@
-@import "../../global/globalCssVariables.scss";
+@import '../../global/globalCssVariables.scss';
+@import '../../../../../node_modules/react-table/react-table.css';
.collectionSchemaView-container {
border-width: $COLLECTION_BORDER_WIDTH;
border-color: $medium-gray;
@@ -218,8 +219,6 @@
}
}
-
-
.collectionSchemaView-header {
height: 100%;
color: gray;
@@ -289,8 +288,6 @@ button.add-column {
}
}
-
-
.keys-dropdown {
position: relative;
//width: 100%;
@@ -300,13 +297,12 @@ button.add-column {
padding: 3px;
height: 28px;
font-weight: bold;
- letter-spacing: "2px";
- text-transform: "uppercase";
+ letter-spacing: '2px';
+ text-transform: 'uppercase';
&:focus {
font-weight: normal;
}
}
-
}
.columnMenu-colors {
display: flex;
@@ -338,7 +334,6 @@ button.add-column {
margin-right: 5px;
font-size: 10px;
border-radius: 3px;
-
}
.keys-options-wrapper {
@@ -348,7 +343,7 @@ button.add-column {
top: 100%;
z-index: 21;
background-color: #ffffff;
- box-shadow: 0px 3px 4px rgba(0,0,0,30%);
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
padding: 1px;
.key-option {
cursor: pointer;
@@ -473,8 +468,8 @@ button.add-column {
}
.collectionSchemaView-cellContents-docButton {
float: right;
- width: "15px";
- height: "15px";
+ width: '15px';
+ height: '15px';
}
.collectionSchemaView-dropdownWrapper {
border: grey;
@@ -490,10 +485,10 @@ button.add-column {
display: inline-block;
//float: right;
height: 100%;
- display: "flex";
+ display: 'flex';
font-size: 13;
- justify-content: "center";
- align-items: "center";
+ justify-content: 'center';
+ align-items: 'center';
}
}
.collectionSchemaView-dropdownContainer {
@@ -601,4 +596,4 @@ button.add-column {
font-size: 10.5px;
margin-left: 50px;
margin-top: 10px;
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 8b73351d5..c4ee1805f 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,30 +1,29 @@
-import React = require("react");
+import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked, trace } from "mobx";
-import { observer } from "mobx-react";
-import Measure from "react-measure";
-import { Resize } from "react-table";
-import { Doc, Opt } from "../../../../fields/Doc";
-import { List } from "../../../../fields/List";
-import { listSpec } from "../../../../fields/Schema";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { Cast, NumCast } from "../../../../fields/Types";
-import { TraceMobx } from "../../../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils";
-import { DocUtils } from "../../../documents/Documents";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import '../../../views/DocumentDecorations.scss';
-import { ContextMenu } from "../../ContextMenu";
-import { ContextMenuProps } from "../../ContextMenuItem";
+import { action, computed, observable, untracked } from 'mobx';
+import { observer } from 'mobx-react';
+import Measure from 'react-measure';
+import { Resize } from 'react-table';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { PastelSchemaPalette, SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+import { Cast, NumCast } from '../../../../fields/Types';
+import { TraceMobx } from '../../../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { DocUtils } from '../../../documents/Documents';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
+import { ContextMenuProps } from '../../ContextMenuItem';
import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { DocumentView } from "../../nodes/DocumentView";
-import { DefaultStyleProvider } from "../../StyleProvider";
-import { CollectionSubView } from "../CollectionSubView";
-import "./CollectionSchemaView.scss";
-import { SchemaTable } from "./SchemaTable";
+import { DocumentView } from '../../nodes/DocumentView';
+import { DefaultStyleProvider } from '../../StyleProvider';
+import { CollectionSubView } from '../CollectionSubView';
+import './CollectionSchemaView.scss';
+import { SchemaTable } from './SchemaTable';
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
export enum ColumnType {
@@ -35,14 +34,21 @@ export enum ColumnType {
Doc,
Image,
List,
- Date
+ 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],
- ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
+ ['title', ColumnType.String],
+ ['x', ColumnType.Number],
+ ['y', ColumnType.Number],
+ ['_width', ColumnType.Number],
+ ['_height', ColumnType.Number],
+ ['_nativeWidth', ColumnType.Number],
+ ['_nativeHeight', ColumnType.Number],
+ ['isPrototype', ColumnType.Boolean],
+ ['_curPage', ColumnType.Number],
+ ['_currentTimecode', ColumnType.Number],
+ ['zIndex', ColumnType.Number],
]);
@observer
@@ -51,7 +57,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _previewDoc: Doc | undefined = undefined;
@observable _focusedTable: Doc = this.props.Document;
- @observable _col: any = "";
+ @observable _col: any = '';
@observable _menuWidth = 0;
@observable _headerOpen = false;
@observable _headerIsEditing = false;
@@ -60,19 +66,33 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _pointerY = 0;
@observable _openTypes: 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 - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); }
- @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
- @computed get scale() { return 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); }
+ @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 - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
+ }
+ @computed get borderWidth() {
+ return Number(COLLECTION_BORDER_WIDTH);
+ }
+ @computed get scale() {
+ return 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);
+ }
@computed get menuCoordinates() {
let searchx = 0;
let searchy = 0;
if (this.props.Document._searchDoc) {
- const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0];
+ const el = document.getElementsByClassName('collectionSchemaView-searchContainer')[0];
if (el !== undefined) {
const rect = el.getBoundingClientRect();
searchx = rect.x;
@@ -93,13 +113,13 @@ export class CollectionSchemaView extends CollectionSubView() {
// 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))));
+ 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);
+ this.columns.forEach(key => (keys[key.heading] = true));
return Array.from(Object.keys(keys));
}
- @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
+ @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing);
@undoBatch
setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
@@ -124,7 +144,7 @@ export class CollectionSchemaView extends CollectionSubView() {
columns[index] = columnField;
this.columns = columns; // need to set the columns to trigger rerender
}
- }
+ };
@undoBatch
@action
@@ -137,80 +157,109 @@ export class CollectionSchemaView extends CollectionSubView() {
column.setDesc(descending);
columns[index] = column;
this.columns = columns;
- }
+ };
renderTypes = (col: any) => {
- if (columnTypes.get(col.heading)) return (null);
+ if (columnTypes.get(col.heading)) return null;
const type = col.type;
- const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}>
- <FontAwesomeIcon icon={"align-justify"} size="sm" />
- Any
- </div>;
-
- const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}>
- <FontAwesomeIcon icon={"hashtag"} size="sm" />
- Number
- </div>;
-
- const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}>
- <FontAwesomeIcon icon={"font"} size="sm" />
- Text
- </div>;
-
- const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
- <FontAwesomeIcon icon={"check-square"} size="sm" />
- Checkbox
- </div>;
-
- const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}>
- <FontAwesomeIcon icon={"list-ul"} size="sm" />
- List
- </div>;
-
- const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
- <FontAwesomeIcon icon={"file"} size="sm" />
- Document
- </div>;
-
- const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}>
- <FontAwesomeIcon icon={"image"} size="sm" />
- Image
- </div>;
-
- const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}>
- <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;
+ const anyType = (
+ <div className={'columnMenu-option' + (type === ColumnType.Any ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Any)}>
+ <FontAwesomeIcon icon={'align-justify'} size="sm" />
+ Any
+ </div>
+ );
+
+ const numType = (
+ <div className={'columnMenu-option' + (type === ColumnType.Number ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Number)}>
+ <FontAwesomeIcon icon={'hashtag'} size="sm" />
+ Number
+ </div>
+ );
+
+ const textType = (
+ <div className={'columnMenu-option' + (type === ColumnType.String ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.String)}>
+ <FontAwesomeIcon icon={'font'} size="sm" />
+ Text
+ </div>
+ );
+
+ const boolType = (
+ <div className={'columnMenu-option' + (type === ColumnType.Boolean ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
+ <FontAwesomeIcon icon={'check-square'} size="sm" />
+ Checkbox
+ </div>
+ );
+
+ const listType = (
+ <div className={'columnMenu-option' + (type === ColumnType.List ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.List)}>
+ <FontAwesomeIcon icon={'list-ul'} size="sm" />
+ List
+ </div>
+ );
+
+ const docType = (
+ <div className={'columnMenu-option' + (type === ColumnType.Doc ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
+ <FontAwesomeIcon icon={'file'} size="sm" />
+ Document
+ </div>
+ );
+
+ const imageType = (
+ <div className={'columnMenu-option' + (type === ColumnType.Image ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Image)}>
+ <FontAwesomeIcon icon={'image'} size="sm" />
+ Image
+ </div>
+ );
+
+ const dateType = (
+ <div className={'columnMenu-option' + (type === ColumnType.Date ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Date)}>
+ <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" onClick={action(() => this._openTypes = !this._openTypes)}>
+ <div className="collectionSchema-headerMenu-group" onClick={action(() => (this._openTypes = !this._openTypes))}>
<div>
- <label style={{ cursor: "pointer" }}>Column type:</label>
- <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right", transform: `rotate(${this._openTypes ? "180deg" : 0})`, transition: "0.2s all ease" }} />
+ <label style={{ cursor: 'pointer' }}>Column type:</label>
+ <FontAwesomeIcon icon={'caret-down'} size="lg" style={{ float: 'right', transform: `rotate(${this._openTypes ? '180deg' : 0})`, transition: '0.2s all ease' }} />
</div>
{this._openTypes ? allColumnTypes : justColType}
- </div >
+ </div>
);
- }
+ };
renderSorting = (col: any) => {
const sort = col.desc;
@@ -218,11 +267,11 @@ export class CollectionSchemaView extends CollectionSubView() {
<div className="collectionSchema-headerMenu-group">
<label>Sort by:</label>
<div className="columnMenu-sort">
- <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.setColumnSort(col, true)}>
+ <div className={'columnMenu-option' + (sort === true ? ' active' : '')} onClick={() => this.setColumnSort(col, true)}>
<FontAwesomeIcon icon="sort-amount-down" size="sm" />
Sort descending
</div>
- <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.setColumnSort(col, false)}>
+ <div className={'columnMenu-option' + (sort === false ? ' active' : '')} onClick={() => this.setColumnSort(col, false)}>
<FontAwesomeIcon icon="sort-amount-up" size="sm" />
Sort ascending
</div>
@@ -233,42 +282,42 @@ export class CollectionSchemaView extends CollectionSubView() {
</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";
+ 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.setColumnColor(col, pink!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
+ <div className={'columnMenu-colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
+ <div className={'columnMenu-colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
+ <div className={'columnMenu-colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
+ <div className={'columnMenu-colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
+ <div className={'columnMenu-colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
+ <div className={'columnMenu-colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
</div>
</div>
);
- }
+ };
@undoBatch
@action
changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
const columns = this.columns;
if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
+ this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, 'f1efeb')]);
} else {
if (addNew) {
- columns.push(new SchemaHeaderField(newKey, "f1efeb"));
+ columns.push(new SchemaHeaderField(newKey, 'f1efeb'));
this.columns = columns;
} else {
const index = columns.map(c => c.heading).indexOf(oldKey);
@@ -278,15 +327,14 @@ export class CollectionSchemaView extends CollectionSubView() {
columns[index] = column;
this.columns = columns;
if (filter) {
- Doc.setDocFilter(this.props.Document, newKey, filter, "match");
- }
- else {
+ Doc.setDocFilter(this.props.Document, newKey, filter, 'match');
+ } else {
this.props.Document._docFilters = undefined;
}
}
}
}
- }
+ };
@action
openHeader = (col: any, screenx: number, screeny: number) => {
@@ -294,10 +342,12 @@ export class CollectionSchemaView extends CollectionSubView() {
this._headerOpen = true;
this._pointerX = screenx;
this._pointerY = screeny;
- }
+ };
@action
- closeHeader = () => { this._headerOpen = false; }
+ closeHeader = () => {
+ this._headerOpen = false;
+ };
@undoBatch
@action
@@ -313,16 +363,16 @@ export class CollectionSchemaView extends CollectionSubView() {
}
}
this.closeHeader();
- }
+ };
getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(- this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, - this.borderWidth);
- }
+ return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth);
+ };
@action
onHeaderClick = (e: React.PointerEvent) => {
e.stopPropagation();
- }
+ };
@action
onWheel(e: React.WheelEvent) {
@@ -332,40 +382,45 @@ export class CollectionSchemaView extends CollectionSubView() {
@computed get renderMenuContent() {
TraceMobx();
- return <div className="collectionSchema-header-menuOptions">
- {this.renderTypes(this._col)}
- {this.renderColors(this._col)}
- <div className="collectionSchema-headerMenu-group">
- <button onClick={() => { this.deleteColumn(this._col.heading); }}>
- Hide Column
- </button>
+ return (
+ <div className="collectionSchema-header-menuOptions">
+ {this.renderTypes(this._col)}
+ {this.renderColors(this._col)}
+ <div className="collectionSchema-headerMenu-group">
+ <button
+ onClick={() => {
+ this.deleteColumn(this._col.heading);
+ }}>
+ Hide Column
+ </button>
+ </div>
</div>
- </div>;
+ );
}
private createTarget = (ele: HTMLDivElement) => {
this._previewCont = ele;
super.CreateDropTarget(ele);
- }
+ };
isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
- @action setFocused = (doc: Doc) => this._focusedTable = doc;
+ @action setFocused = (doc: Doc) => (this._focusedTable = doc);
@action setPreviewDoc = (doc: Opt<Doc>) => {
SelectionManager.SelectSchemaViewDoc(doc);
this._previewDoc = doc;
- }
+ };
//toggles preview side-panel of schema
@action
toggleExpander = () => {
this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
- }
+ };
onDividerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
- }
+ };
@action
onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
const nativeWidth = this._previewCont!.getBoundingClientRect();
@@ -375,138 +430,146 @@ export class CollectionSchemaView extends CollectionSubView() {
const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
this.props.Document.schemaPreviewWidth = width;
return false;
- }
+ };
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (this.props.isSelected(true)) e.stopPropagation();
else this.props.select(false);
}
- }
+ };
@computed
- get previewDocument(): Doc | undefined { return this._previewDoc; }
+ get previewDocument(): Doc | undefined {
+ return this._previewDoc;
+ }
@computed
get dividerDragger() {
- return this.previewWidth() === 0 ? (null) :
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} >
+ return this.previewWidth() === 0 ? null : (
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown}>
<div className="collectionSchemaView-dividerDragger" />
- </div>;
+ </div>
+ );
}
@computed
get previewPanel() {
- return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
- {!this.previewDocument ? (null) :
- <DocumentView
- Document={this.previewDocument}
- DataDoc={undefined}
- fitContentsToDoc={returnTrue}
- freezeDimensions={true}
- dontCenter={"y"}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={this.rootSelected}
- PanelWidth={this.previewWidth}
- PanelHeight={this.previewHeight}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- docViewPath={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
- />}
- </div>;
+ return (
+ <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
+ {!this.previewDocument ? null : (
+ <DocumentView
+ Document={this.previewDocument}
+ DataDoc={undefined}
+ fitContentsToBox={returnTrue}
+ dontCenter={'y'}
+ focus={DocUtils.DefaultFocus}
+ renderDepth={this.props.renderDepth}
+ rootSelected={this.rootSelected}
+ PanelWidth={this.previewWidth}
+ PanelHeight={this.previewHeight}
+ isContentActive={returnTrue}
+ isDocumentActive={returnFalse}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ styleProvider={DefaultStyleProvider}
+ docViewPath={returnEmptyDoclist}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ />
+ )}
+ </div>
+ );
}
@computed
get schemaTable() {
- return <SchemaTable
- Document={this.props.Document}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- childDocs={this.childDocs}
- CollectionView={this.props.CollectionView}
- ContainingCollectionView={this.props.ContainingCollectionView}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- fieldKey={this.props.fieldKey}
- renderDepth={this.props.renderDepth}
- moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- active={this.props.isContentActive}
- onDrop={this.onExternalDrop}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- isSelected={this.props.isSelected}
- isFocused={this.isFocused}
- setFocused={this.setFocused}
- setPreviewDoc={this.setPreviewDoc}
- deleteDocument={this.props.removeDocument}
- addDocument={this.props.addDocument}
- dataDoc={this.props.DataDoc}
- columns={this.columns}
- documentKeys={this.documentKeys}
- headerIsEditing={this._headerIsEditing}
- openHeader={this.openHeader}
- onClick={this.onTableClick}
- onPointerDown={emptyFunction}
- onResizedChange={this.onResizedChange}
- setColumns={this.setColumns}
- reorderColumns={this.reorderColumns}
- changeColumns={this.changeColumns}
- setHeaderIsEditing={this.setHeaderIsEditing}
- changeColumnSort={this.setColumnSort}
- />;
+ return (
+ <SchemaTable
+ Document={this.props.Document}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ childDocs={this.childDocs}
+ CollectionView={this.props.CollectionView}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ fieldKey={this.props.fieldKey}
+ renderDepth={this.props.renderDepth}
+ moveDocument={this.props.moveDocument}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ active={this.props.isContentActive}
+ onDrop={this.onExternalDrop}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ isSelected={this.props.isSelected}
+ isFocused={this.isFocused}
+ setFocused={this.setFocused}
+ setPreviewDoc={this.setPreviewDoc}
+ deleteDocument={this.props.removeDocument}
+ addDocument={this.props.addDocument}
+ dataDoc={this.props.DataDoc}
+ columns={this.columns}
+ documentKeys={this.documentKeys}
+ headerIsEditing={this._headerIsEditing}
+ openHeader={this.openHeader}
+ onClick={this.onTableClick}
+ onPointerDown={emptyFunction}
+ onResizedChange={this.onResizedChange}
+ setColumns={this.setColumns}
+ reorderColumns={this.reorderColumns}
+ changeColumns={this.changeColumns}
+ setHeaderIsEditing={this.setHeaderIsEditing}
+ changeColumnSort={this.setColumnSort}
+ />
+ );
}
@computed
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
+ 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>
</div>
- </div>;
+ );
}
onSpecificMenu = (e: React.MouseEvent) => {
- if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) {
+ if ((e.target as any)?.className?.includes?.('collectionSchemaView-cell') || e.target instanceof HTMLSpanElement) {
const cm = ContextMenu.Instance;
- const options = cm.findByDescription("Options...");
- const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: "remove", event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: "trash" });
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+ const options = cm.findByDescription('Options...');
+ const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+ optionItems.push({ description: 'remove', event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: 'trash' });
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
cm.displayMenu(e.clientX, e.clientY);
(e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
e.stopPropagation();
}
- }
+ };
@action
onTableClick = (e: React.MouseEvent): void => {
- if (!(e.target as any)?.className?.includes?.("collectionSchemaView-cell") && !(e.target instanceof HTMLSpanElement)) {
+ if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) {
this.setPreviewDoc(undefined);
} else {
e.stopPropagation();
}
this.setFocused(this.props.Document);
this.closeHeader();
- }
+ };
onResizedChange = (newResized: Resize[], event: any) => {
const columns = this.columns;
@@ -517,23 +580,23 @@ export class CollectionSchemaView extends CollectionSubView() {
columns[index] = column;
});
this.columns = columns;
- }
+ };
@action
- setColumns = (columns: SchemaHeaderField[]) => this.columns = columns
+ setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns);
@undoBatch
reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
const columns = [...columnsValues];
const oldIndex = columns.indexOf(toMove);
const relIndex = columns.indexOf(relativeTo);
- const newIndex = (oldIndex > relIndex && !before) ? relIndex + 1 : (oldIndex < relIndex && before) ? relIndex - 1 : relIndex;
+ const newIndex = oldIndex > relIndex && !before ? relIndex + 1 : oldIndex < relIndex && before ? relIndex - 1 : relIndex;
if (oldIndex === newIndex) return;
columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
this.columns = columns;
- }
+ };
onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
@@ -541,35 +604,44 @@ export class CollectionSchemaView extends CollectionSubView() {
TraceMobx();
if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0);
const menuContent = this.renderMenuContent;
- const menu = <div className="collectionSchema-header-menu"
- onWheel={e => this.onZoomMenu(e)}
- onPointerDown={e => this.onHeaderClick(e)}
- style={{ transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}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" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")}
- style={{
- overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white",
- pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined,
- width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative",
- }} >
- <div className="collectionSchemaView-tableContainer"
- style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
- onContextMenu={this.onSpecificMenu}
- onPointerDown={this.onPointerDown}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
- onDrop={e => this.onExternalDrop(e, {})}
- ref={this.createTarget}>
- {this.schemaTable}
+ const menu = (
+ <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}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>
- {this.dividerDragger}
- {!this.previewWidth() ? (null) : this.previewPanel}
- {this._headerOpen && this.props.isContentActive() ? menu : null}
- </div>;
+ );
+ return (
+ <div
+ className={'collectionSchemaView' + (this.props.Document._searchDoc ? '-searchContainer' : '-container')}
+ style={{
+ overflow: this.props.scrollOverflow === true ? 'scroll' : undefined,
+ backgroundColor: 'white',
+ pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+ width: this.props.PanelWidth() || '100%',
+ height: this.props.PanelHeight() || '100%',
+ position: 'relative',
+ }}>
+ <div
+ className="collectionSchemaView-tableContainer"
+ style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
+ onContextMenu={this.onSpecificMenu}
+ onPointerDown={this.onPointerDown}
+ onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
+ onDrop={e => this.onExternalDrop(e, {})}
+ ref={this.createTarget}>
+ {this.schemaTable}
+ </div>
+ {this.dividerDragger}
+ {!this.previewWidth() ? null : this.previewPanel}
+ {this._headerOpen && this.props.isContentActive() ? menu : null}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
index 605481ddf..fafea5ce3 100644
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx
@@ -1,37 +1,47 @@
-import React = require("react");
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, trace } from "mobx";
-import { observer } from "mobx-react";
-import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
-import { DateField } from "../../../../fields/DateField";
-import { AclPrivate, AclReadonly, DataSym, 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 } from "../../../../fields/Types";
-import { ImageField } from "../../../../fields/URLField";
-import { GetEffectiveAcl } from "../../../../fields/util";
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../../Utils";
-import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { CompileScript, Transformer, ts } from "../../../util/Scripting";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { ContextMenu } from "../../ContextMenu";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table';
+import { DateField } from '../../../../fields/DateField';
+import { AclPrivate, AclReadonly, DataSym, 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 } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { GetEffectiveAcl } from '../../../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils';
+import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { CompileScript, Transformer, ts } from '../../../util/Scripting';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
import '../../../views/DocumentDecorations.scss';
-import { DocumentView } from "../../nodes/DocumentView";
-import { DefaultStyleProvider } from "../../StyleProvider";
-import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
-import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
-import { MovableColumn } from "./CollectionSchemaMovableColumn";
-import { MovableRow } from "./CollectionSchemaMovableRow";
-import "./CollectionSchemaView.scss";
-import { CollectionView } from "../CollectionView";
-
+import { ContextMenu } from '../../ContextMenu';
+import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
+import { DocumentView } from '../../nodes/DocumentView';
+import { DefaultStyleProvider } from '../../StyleProvider';
+import { CollectionView } from '../CollectionView';
+import {
+ CellProps,
+ CollectionSchemaButtons,
+ CollectionSchemaCell,
+ CollectionSchemaCheckboxCell,
+ CollectionSchemaDateCell,
+ CollectionSchemaDocCell,
+ CollectionSchemaImageCell,
+ CollectionSchemaListCell,
+ CollectionSchemaNumberCell,
+ CollectionSchemaStringCell,
+} from './CollectionSchemaCells';
+import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders';
+import { MovableColumn } from './CollectionSchemaMovableColumn';
+import { MovableRow } from './CollectionSchemaMovableRow';
+import './CollectionSchemaView.scss';
enum ColumnType {
Any,
@@ -41,15 +51,22 @@ enum ColumnType {
Doc,
Image,
List,
- Date
+ 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],
- ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
+ ['title', ColumnType.String],
+ ['x', ColumnType.Number],
+ ['y', ColumnType.Number],
+ ['_width', ColumnType.Number],
+ ['_height', ColumnType.Number],
+ ['_nativeWidth', ColumnType.Number],
+ ['_nativeHeight', ColumnType.Number],
+ ['isPrototype', ColumnType.Boolean],
+ ['_curPage', ColumnType.Number],
+ ['_currentTimecode', ColumnType.Number],
+ ['zIndex', ColumnType.Number],
]);
export interface SchemaTableProps {
@@ -92,18 +109,24 @@ export interface SchemaTableProps {
@observer
export class SchemaTable extends React.Component<SchemaTableProps> {
@observable _cellIsEditing: boolean = false;
- @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
- @observable _openCollections: Set<number> = new Set;
+ @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 };
+ @observable _openCollections: Set<number> = new Set();
@observable _showDoc: Doc | undefined;
- @observable _showDataDoc: any = "";
+ @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 - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); }
+ @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 - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
+ }
@computed get childDocs() {
if (this.props.childDocs) return this.props.childDocs;
@@ -117,17 +140,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@computed get textWrappedRows() {
- return Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ 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 }[] {
+ @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 });
+ shf.width > -1 && resized.push({ id: shf.heading, value: shf.width });
return resized;
- }, [] as { id: string, value: number }[]);
+ }, [] as { id: string; value: number }[]);
}
@computed get sorted(): SortingRule[] {
return this.props.columns.reduce((sorted, shf) => {
@@ -139,12 +162,14 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@action
changeSorting = (col: any) => {
this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true);
- }
+ };
@action
- changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown
+ changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown);
- @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @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>[] = [];
@@ -154,149 +179,185 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const isEditable = !this.props.headerIsEditing;
columns.push({
- expander: true, Header: "", width: 58,
- Expander: (rowInfo) => {
- return rowInfo.original.type !== DocumentType.COL ? (null) :
- <div className="collectionSchemaView-expander" onClick={action(() => (this._openCollections[rowInfo.isExpanded ? "delete" : "add"])(rowInfo.viewIndex))}>
- <FontAwesomeIcon icon={rowInfo.isExpanded ? "caret-down" : "caret-right"} size="lg" />
- </div>;
- }
+ expander: true,
+ Header: '',
+ width: 58,
+ Expander: rowInfo => {
+ return rowInfo.original.type !== DocumentType.COL ? null : (
+ <div className="collectionSchemaView-expander" onClick={action(() => this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}>
+ <FontAwesomeIcon icon={rowInfo.isExpanded ? 'caret-down' : 'caret-right'} size="lg" />
+ </div>
+ );
+ },
});
- columns.push(...this.props.columns.map(col => {
- 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 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}
- docs={this.props.childDocs}
- Document={this.props.Document}
- dataDoc={this.props.dataDoc}
- fieldKey={this.props.fieldKey}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- active={this.props.active}
- openHeader={this.props.openHeader}
- icon={icon}
- col={col}
- // try commenting this out
- width={"100%"}
- />;
-
- const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
- const header = <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}>
- {keysDropdown}
- <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "pointer" }}>
- <FontAwesomeIcon icon={sortIcon} size="lg" />
- </div>
- {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</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 ? Field.toString(doc[col.heading] as Field) : 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,
- };
-
-
- switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
- case ColumnType.Number: return <CollectionSchemaNumberCell {...props} />;
- case ColumnType.String: return <CollectionSchemaStringCell {...props} />;
- case ColumnType.Boolean: return <CollectionSchemaCheckboxCell {...props} />;
- case ColumnType.Doc: return <CollectionSchemaDocCell {...props} />;
- case ColumnType.Image: return <CollectionSchemaImageCell {...props} />;
- case ColumnType.List: return <CollectionSchemaListCell {...props} />;
- case ColumnType.Date: return <CollectionSchemaDateCell {...props} />;
- default:
- return <CollectionSchemaCell {...props} />;
- }
- },
- minWidth: 200,
- };
- }));
+ columns.push(
+ ...this.props.columns.map(col => {
+ 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 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}
+ docs={this.props.childDocs}
+ Document={this.props.Document}
+ dataDoc={this.props.dataDoc}
+ fieldKey={this.props.fieldKey}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ active={this.props.active}
+ openHeader={this.props.openHeader}
+ icon={icon}
+ col={col}
+ // try commenting this out
+ width={'100%'}
+ />
+ );
+
+ const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up';
+ const header = (
+ <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: '2px', display: 'flex', cursor: 'default', height: '100%' }}>
+ {keysDropdown}
+ <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}>
+ <FontAwesomeIcon icon={sortIcon} size="lg" />
+ </div>
+ {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</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 ? Field.toString(doc[col.heading] as Field) : 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,
+ };
+
+ switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
+ case ColumnType.Number:
+ return <CollectionSchemaNumberCell {...props} />;
+ case ColumnType.String:
+ return <CollectionSchemaStringCell {...props} />;
+ case ColumnType.Boolean:
+ return <CollectionSchemaCheckboxCell {...props} />;
+ case ColumnType.Doc:
+ return <CollectionSchemaDocCell {...props} />;
+ case ColumnType.Image:
+ return <CollectionSchemaImageCell {...props} />;
+ case ColumnType.List:
+ return <CollectionSchemaListCell {...props} />;
+ case ColumnType.Date:
+ return <CollectionSchemaDateCell {...props} />;
+ default:
+ return <CollectionSchemaCell {...props} />;
+ }
+ },
+ minWidth: 200,
+ };
+ })
+ );
columns.push({
Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
accessor: (doc: Doc) => 0,
- id: "add",
+ id: 'add',
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;
- return <CollectionSchemaButtons {...{
- row: rowProps.index,
- 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,
- }} />;
+ return (
+ <CollectionSchemaButtons
+ {...{
+ row: rowProps.index,
+ 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,
+ }}
+ />
+ );
},
width: 28,
- resizable: false
+ resizable: false,
});
return columns;
}
-
constructor(props: SchemaTableProps) {
super(props);
if (this.props.Document._schemaHeaders === undefined) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb", ColumnType.Date),
- new SchemaHeaderField("text", "#f1efeb", ColumnType.String), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb", ColumnType.Doc)]);
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>([
+ new SchemaHeaderField('title', '#f1efeb'),
+ new SchemaHeaderField('author', '#f1efeb'),
+ new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date),
+ new SchemaHeaderField('text', '#f1efeb', ColumnType.String),
+ new SchemaHeaderField('type', '#f1efeb'),
+ new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc),
+ ]);
}
}
componentDidMount() {
- document.addEventListener("keydown", this.onKeyDown);
+ document.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
- document.removeEventListener("keydown", this.onKeyDown);
+ document.removeEventListener('keydown', this.onKeyDown);
}
tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
@@ -305,25 +366,27 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) {
doc.context = this.props.Document;
- tableDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()));
return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
}
return false;
- }
+ };
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, true),
- textWrapRow: this.toggleTextWrapRow,
- rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
- dropAction: StrCast(this.props.Document.childDropAction),
- addDocTab: this.props.addDocTab
- };
- }
+ 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, true),
+ 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 {};
@@ -334,16 +397,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true);
// TODO: editing border doesn't work :(
return {
- style: { border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb" }
+ style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' },
};
- }
+ };
- @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing;
+ @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing);
@action
onKeyDown = (e: KeyboardEvent): void => {
- if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {// && 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" : "";
+ if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {
+ // && 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);
if (direction) {
@@ -353,20 +417,25 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
} else if (e.keyCode === 27) {
this.props.setPreviewDoc(undefined);
- e.stopPropagation(); // stopPropagation for left/right arrows
+ e.stopPropagation(); // stopPropagation for left/right arrows
}
- }
+ };
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 };
+ 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 => {
@@ -374,25 +443,25 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
this._focusedCell = { row: row, col: col };
}
this.props.setFocused(this.props.Document);
- }
+ };
@undoBatch
createRow = action(() => {
- this.props.addDocument?.(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
+ this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 }));
this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
});
@undoBatch
@action
createColumn = () => {
- const newFieldName = (index: number) => `New field${index ? ` (${index})` : ""}`;
+ const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`;
for (let index = 0; index < 100; index++) {
if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) {
- this.props.columns.push(new SchemaHeaderField(newFieldName(index), "#f1efeb"));
+ this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb'));
break;
}
}
- }
+ };
@action
getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => {
@@ -407,15 +476,15 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return column.type;
}
if (columnTypes.get(column.heading)) {
- return column.type = columnTypes.get(column.heading)!;
+ return (column.type = columnTypes.get(column.heading)!);
}
- return column.type = ColumnType.Any;
- }
+ return (column.type = ColumnType.Any);
+ };
@undoBatch
@action
toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
if (textwrappedRows.length) {
this.props.Document.textwrappedSchemaRows = new List<string>([]);
} else {
@@ -423,7 +492,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
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 => {
@@ -433,41 +502,50 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
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 === DocumentType.COL, false);
const expanded: { [name: string]: any } = {};
- Array.from(this._openCollections.keys()).map(col => expanded[col.toString()] = true);
+ Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = 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}
- // if it has a child, render another table with the children
- SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== DocumentType.COL) ? (null) :
- <div style={{ paddingLeft: 57 + "px" }} className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
-
- />;
+ 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}
+ // if it has a child, render another table with the children
+ SubComponent={
+ !hasCollectionChild
+ ? undefined
+ : row =>
+ row.original.type !== DocumentType.COL ? null : (
+ <div style={{ paddingLeft: 57 + 'px' }} className="reactTable-sub">
+ <SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} />
+ </div>
+ )
+ }
+ />
+ );
}
onContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
- }
+ ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' });
+ };
getField = (row: number, col?: number) => {
const docs = this.childDocs;
@@ -484,7 +562,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return doc[column];
}
return undefined;
- }
+ };
createTransformer = (row: number, col: number): Transformer => {
const self = this;
@@ -498,11 +576,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
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") {
+ if (node.text === '$r') {
return ts.createNumericLiteral(row.toString());
- } else if (node.text === "$c") {
+ } else if (node.text === '$c') {
return ts.createNumericLiteral(col.toString());
- } else if (node.text === "$") {
+ } else if (node.text === '$') {
if (ts.isCallExpression(node.parent)) {
// captures.doc = self.props.Document;
// captures.key = self.props.fieldKey;
@@ -521,12 +599,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
// return { capturedVariables: captures };
// };
- return { transformer, /*getVars*/ };
- }
+ return { transformer /*getVars*/ };
+ };
setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
- script =
- `const $ = (row:number, col?:number) => {
+ script = `const $ = (row:number, col?:number) => {
const rval = (doc as any)[key][row + ${row}];
return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading];
}
@@ -537,7 +614,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return true;
}
return false;
- }
+ };
@action
showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => {
@@ -545,57 +622,72 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
if (dataDoc && screenX && screenY) {
this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY);
}
- }
+ };
onOpenClick = () => {
- this._showDoc && this.props.addDocTab(this._showDoc, "add:right");
- }
+ this._showDoc && this.props.addDocTab(this._showDoc, 'add:right');
+ };
getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(- this.borderWidth - 4 - this.tableWidth, - this.borderWidth);
- }
+ return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth);
+ };
render() {
- const preview = "";
- return <div className="collectionSchemaView-table"
- onPointerDown={this.props.onPointerDown} onClick={this.props.onClick} onWheel={e => this.props.active(true) && e.stopPropagation()}
- onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
- {this.reactTable}
- {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>}
- {!this._showDoc ? (null) :
- <div className="collectionSchemaView-documentPreview" ref="overlay"
- 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)`
- }} >
- <DocumentView
- Document={this._showDoc}
- DataDoc={this._showDataDoc}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- docViewPath={returnEmptyDoclist}
- freezeDimensions={true}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={returnFalse}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- PanelWidth={() => 150}
- PanelHeight={() => 150}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- whenChildContentsActiveChanged={emptyFunction}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}>
- </DocumentView>
- </div>}
- </div>;
+ const preview = '';
+ return (
+ <div
+ className="collectionSchemaView-table"
+ onPointerDown={this.props.onPointerDown}
+ onClick={this.props.onClick}
+ onWheel={e => this.props.active(true) && e.stopPropagation()}
+ onDrop={e => this.props.onDrop(e, {})}
+ onContextMenu={this.onContextMenu}>
+ {this.reactTable}
+ {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : (
+ <div className="collectionSchemaView-addRow" onClick={this.createRow}>
+ + new
+ </div>
+ )}
+ {!this._showDoc ? null : (
+ <div
+ className="collectionSchemaView-documentPreview"
+ ref="overlay"
+ 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)`,
+ }}>
+ <DocumentView
+ Document={this._showDoc}
+ DataDoc={this._showDataDoc}
+ styleProvider={DefaultStyleProvider}
+ docViewPath={returnEmptyDoclist}
+ focus={DocUtils.DefaultFocus}
+ renderDepth={this.props.renderDepth}
+ rootSelected={returnFalse}
+ isContentActive={returnTrue}
+ isDocumentActive={returnFalse}
+ PanelWidth={() => 150}
+ PanelHeight={() => 150}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ whenChildContentsActiveChanged={emptyFunction}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}></DocumentView>
+ </div>
+ )}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 520ac9357..a14634bdc 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -10,7 +10,7 @@ $black: #000000;
$light-blue: #bdddf5;
$light-blue-transparent: #bdddf590;
$medium-blue: #4476f7;
-$medium-blue-alt: #4476f73d;
+$medium-blue-alt: #0047ff54;
$pink: #e0217d;
$yellow: #f5d747;
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 56779c37c..610c2b102 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -8,6 +8,7 @@ export enum Colors {
MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY
LIGHT_BLUE = "#BDDDF5",
PINK = "#E0217D",
+ ERROR_RED = "#ff0033",
YELLOW = "#F5D747",
DROP_SHADOW = "#32323215",
}
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index abd413f57..1d6496d3c 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -60,6 +60,24 @@
}
}
+.linkEditor-zoomFollow {
+ padding-left: 26px;
+ padding-right: 6.5px;
+ padding-bottom: 3.5px;
+ display: flex;
+
+ .linkEditor-zoomFollow-label {
+ text-decoration-color: black;
+ color: black;
+ line-height: 1.7;
+ }
+
+ .linkEditor-zoomFollow-input {
+ display: block;
+ width: 20px;
+ }
+}
+
.linkEditor-description {
padding-left: 26px;
padding-right: 6.5px;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index db331bb75..ba301962b 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,16 +1,14 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, NumListCast, StrListCast, Field } from "../../../fields/Doc";
-import { DateCast, StrCast, Cast } from "../../../fields/Types";
-import { LinkManager } from "../../util/LinkManager";
-import { undoBatch } from "../../util/UndoManager";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, NumListCast, StrListCast, Field } from '../../../fields/Doc';
+import { DateCast, StrCast, Cast } from '../../../fields/Types';
+import { LinkManager } from '../../util/LinkManager';
+import { undoBatch } from '../../util/UndoManager';
import './LinkEditor.scss';
-import { LinkRelationshipSearch } from "./LinkRelationshipSearch";
-import React = require("react");
-import { ToString } from "../../../fields/FieldSymbols";
-
+import { LinkRelationshipSearch } from './LinkRelationshipSearch';
+import React = require('react');
interface LinkEditorProps {
sourceDoc: Doc;
@@ -20,15 +18,20 @@ interface LinkEditorProps {
}
@observer
export class LinkEditor extends React.Component<LinkEditorProps> {
-
@observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
@observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
+ @observable zoomFollow = StrCast(this.props.sourceDoc.followLinkZoom);
@observable openDropdown: boolean = false;
@observable showInfo: boolean = false;
- @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
- @observable private buttonColor: string = "";
- @observable private relationshipButtonColor: string = "";
- @observable private relationshipSearchVisibility: string = "none";
+ @computed get infoIcon() {
+ if (this.showInfo) {
+ return 'chevron-up';
+ }
+ return 'chevron-down';
+ }
+ @observable private buttonColor: string = '';
+ @observable private relationshipButtonColor: string = '';
+ @observable private relationshipSearchVisibility: string = 'none';
@observable private searchIsActive: boolean = false;
//@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
@@ -37,7 +40,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
deleteLink = (): void => {
LinkManager.Instance.deleteLink(this.props.linkDoc);
this.props.showLinks();
- }
+ };
@undoBatch
setRelationshipValue = action((value: string) => {
@@ -49,11 +52,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes);
const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
- // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
+ // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
if (!linkRelationshipList?.includes(value)) {
linkRelationshipList.push(value);
linkRelationshipSizes.push(1);
- const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")";
+ const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')';
linkColorList.push(randColor);
// if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
} else if (linkRelationshipList && value !== prevRelationship) {
@@ -61,20 +64,22 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
//increment size of new relationship size
if (index !== -1 && index < linkRelationshipSizes.length) {
const pvalue = linkRelationshipSizes[index];
- linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1);
+ linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1;
}
//decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
if (linkRelationshipList.includes(prevRelationship)) {
const pindex = linkRelationshipList.indexOf(prevRelationship);
if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
const pvalue = linkRelationshipSizes[pindex];
- linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1));
+ linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1);
}
}
-
}
- this.relationshipButtonColor = "rgb(62, 133, 55)";
- setTimeout(action(() => this.relationshipButtonColor = ""), 750);
+ this.relationshipButtonColor = 'rgb(62, 133, 55)';
+ setTimeout(
+ action(() => (this.relationshipButtonColor = '')),
+ 750
+ );
return true;
}
});
@@ -89,126 +94,143 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
if (linkRelationshipList) {
return linkRelationshipList.filter(rel => rel.includes(query));
}
- }
+ };
/**
* toggles visibility of the relationship search results when the input field is focused on
*/
@action
toggleRelationshipResults = () => {
- this.relationshipSearchVisibility = this.relationshipSearchVisibility === "none" ? "block" : "none";
- }
+ this.relationshipSearchVisibility = this.relationshipSearchVisibility === 'none' ? 'block' : 'none';
+ };
@undoBatch
setDescripValue = action((value: string) => {
if (LinkManager.currentLink) {
Doc.GetProto(LinkManager.currentLink).description = value;
- this.buttonColor = "rgb(62, 133, 55)";
- setTimeout(action(() => this.buttonColor = ""), 750);
+ this.buttonColor = 'rgb(62, 133, 55)';
+ setTimeout(
+ action(() => (this.buttonColor = '')),
+ 750
+ );
return true;
}
});
onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
this.setDescripValue(this.description);
document.getElementById('input')?.blur();
}
- }
+ e.stopPropagation();
+ };
onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
this.setRelationshipValue(this.relationship);
document.getElementById('input')?.blur();
}
- }
+ e.stopPropagation();
+ };
onDescriptionDown = () => this.setDescripValue(this.description);
onRelationshipDown = () => this.setRelationshipValue(this.relationship);
onBlur = () => {
- //only hide the search results if the user clicks out of the input AND not on any of the search results
+ //only hide the search results if the user clicks out of the input AND not on any of the search results
// i.e. if search is not active
if (!this.searchIsActive) {
this.toggleRelationshipResults();
}
- }
+ };
onFocus = () => {
this.toggleRelationshipResults();
- }
+ };
toggleSearchIsActive = () => {
this.searchIsActive = !this.searchIsActive;
- }
+ };
@action
- handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
+ handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.description = e.target.value;
+ };
@action
handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.relationship = e.target.value;
- }
+ };
+ @action
+ handleZoomFollowChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.sourceDoc.followLinkZoom = e.target.checked;
+ };
@action
handleRelationshipSearchChange = (result: string) => {
this.setRelationshipValue(result);
this.toggleRelationshipResults();
this.relationship = result;
- }
+ };
@computed
get editRelationship() {
//NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
- return <div className="linkEditor-description">
- <div className="linkEditor-description-label">Link Relationship:</div>
- <div className="linkEditor-description-input">
- <div className="linkEditor-description-editing">
- <input
- style={{ width: "100%" }}
- id="input"
- value={this.relationship}
- autoComplete={"off"}
- placeholder={"Enter link relationship"}
- onKeyDown={this.onRelationshipKey}
- onChange={this.handleRelationshipChange}
- onFocus={this.onFocus}
- onBlur={this.onBlur}
- ></input>
- <LinkRelationshipSearch
- results={this.getRelationshipResults()}
- display={this.relationshipSearchVisibility}
- handleRelationshipSearchChange={this.handleRelationshipSearchChange}
- toggleSearch={this.toggleSearchIsActive}
- />
+ return (
+ <div className="linkEditor-description">
+ <div className="linkEditor-description-label">Link Relationship:</div>
+ <div className="linkEditor-description-input">
+ <div className="linkEditor-description-editing">
+ <input
+ style={{ width: '100%' }}
+ id="input"
+ value={this.relationship}
+ autoComplete={'off'}
+ placeholder={'Enter link relationship'}
+ onKeyDown={this.onRelationshipKey}
+ onChange={this.handleRelationshipChange}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}></input>
+ <LinkRelationshipSearch results={this.getRelationshipResults()} display={this.relationshipSearchVisibility} handleRelationshipSearchChange={this.handleRelationshipSearchChange} toggleSearch={this.toggleSearchIsActive} />
+ </div>
+ <div className="linkEditor-description-add-button" style={{ background: this.relationshipButtonColor }} onPointerDown={this.onRelationshipDown}>
+ Set
+ </div>
</div>
- <div className="linkEditor-description-add-button"
- style={{ background: this.relationshipButtonColor }}
- onPointerDown={this.onRelationshipDown}>Set</div>
</div>
- </div>;
+ );
+ }
+ @computed
+ get editZoomFollow() {
+ //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
+ return (
+ <div className="linkEditor-zoomFollow">
+ <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div>
+ <div className="linkEditor-zoomFollow-input">
+ <div className="linkEditor-zoomFollow-editing">
+ <input style={{ width: '100%' }} type="checkbox" value={this.zoomFollow} onChange={this.handleZoomFollowChange} />
+ </div>
+ </div>
+ </div>
+ );
}
@computed
get editDescription() {
- return <div className="linkEditor-description">
- <div className="linkEditor-description-label">Link Description:</div>
- <div className="linkEditor-description-input">
- <div className="linkEditor-description-editing">
- <input
- style={{ width: "100%" }}
- autoComplete={"off"}
- id="input"
- value={this.description}
- placeholder={"Enter link description"}
- onKeyDown={this.onDescriptionKey}
- onChange={this.handleDescriptionChange}
- ></input>
+ return (
+ <div className="linkEditor-description">
+ <div className="linkEditor-description-label">Link Description:</div>
+ <div className="linkEditor-description-input">
+ <div className="linkEditor-description-editing">
+ <input style={{ width: '100%' }} autoComplete={'off'} id="input" value={this.description} placeholder={'Enter link description'} onKeyDown={this.onDescriptionKey} onChange={this.handleDescriptionChange}></input>
+ </div>
+ <div className="linkEditor-description-add-button" style={{ background: this.buttonColor }} onPointerDown={this.onDescriptionDown}>
+ Set
+ </div>
</div>
- <div className="linkEditor-description-add-button"
- style={{ background: this.buttonColor }}
- onPointerDown={this.onDescriptionDown}>Set</div>
</div>
- </div>;
+ );
}
@action
- changeDropdown = () => { this.openDropdown = !this.openDropdown; }
+ changeDropdown = () => {
+ this.openDropdown = !this.openDropdown;
+ };
@undoBatch
changeFollowBehavior = action((follow: string) => {
@@ -218,94 +240,114 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@computed
get followingDropdown() {
- return <div className="linkEditor-followingDropdown">
- <div className="linkEditor-followingDropdown-label">Follow Behavior:</div>
- <div className="linkEditor-followingDropdown-dropdown">
- <div className="linkEditor-followingDropdown-header"
- onPointerDown={this.changeDropdown}>
- {StrCast(this.props.linkDoc.followLinkLocation, "default")}
- <FontAwesomeIcon className="linkEditor-followingDropdown-icon"
- icon={this.openDropdown ? "chevron-up" : "chevron-down"}
- size={"lg"} />
- </div>
- <div className="linkEditor-followingDropdown-optionsList"
- style={{ display: this.openDropdown ? "" : "none" }}>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("default")}>
- Default
- </div>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("add:left")}>
- Always open in new left pane
- </div>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("add:right")}>
- Always open in new right pane
- </div>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("replace:right")}>
- Always replace right tab
- </div>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("replace:left")}>
- Always replace left tab
+ return (
+ <div className="linkEditor-followingDropdown">
+ <div className="linkEditor-followingDropdown-label">Follow Behavior:</div>
+ <div className="linkEditor-followingDropdown-dropdown">
+ <div className="linkEditor-followingDropdown-header" onPointerDown={this.changeDropdown}>
+ {StrCast(this.props.linkDoc.followLinkLocation, 'default')}
+ <FontAwesomeIcon className="linkEditor-followingDropdown-icon" icon={this.openDropdown ? 'chevron-up' : 'chevron-down'} size={'lg'} />
</div>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("fullScreen")}>
- Always open full screen
- </div>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("add")}>
- Always open in a new tab
- </div>
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("replace")}>
- Replace Tab
- </div>
- {this.props.linkDoc.linksToAnnotation ?
- <div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("openExternal")}>
- Always open in external page
+ <div className="linkEditor-followingDropdown-optionsList" style={{ display: this.openDropdown ? '' : 'none' }}>
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('default')}>
+ Default
+ </div>
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:left')}>
+ Always open in new left pane
+ </div>
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:right')}>
+ Always open in new right pane
+ </div>
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:right')}>
+ Always replace right tab
</div>
- : null}
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:left')}>
+ Always replace left tab
+ </div>
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('fullScreen')}>
+ Always open full screen
+ </div>
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add')}>
+ Always open in a new tab
+ </div>
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace')}>
+ Replace Tab
+ </div>
+ {this.props.linkDoc.linksToAnnotation ? (
+ <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('openExternal')}>
+ Always open in external page
+ </div>
+ ) : null}
+ </div>
</div>
</div>
- </div>;
+ );
}
@action
- changeInfo = () => { this.showInfo = !this.showInfo; }
+ changeInfo = () => {
+ this.showInfo = !this.showInfo;
+ };
render() {
const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
-
- return !destination ? (null) : (
- <div className="linkEditor">
+ return !destination ? null : (
+ <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()}>
<div className="linkEditor-info">
- <Tooltip title={<><div className="dash-tooltip">Return to link menu</div></>} placement="top">
- <button className="linkEditor-button-back"
- style={{ display: this.props.hideback ? "none" : "" }}
- onClick={this.props.showLinks}>
- <FontAwesomeIcon icon="arrow-left" size="sm" /> </button>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">Return to link menu</div>
+ </>
+ }
+ placement="top">
+ <button className="linkEditor-button-back" style={{ display: this.props.hideback ? 'none' : '' }} onClick={this.props.showLinks}>
+ <FontAwesomeIcon icon="arrow-left" size="sm" />{' '}
+ </button>
</Tooltip>
- <p className="linkEditor-linkedTo">Editing Link to: <b>{
- destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
- <Tooltip title={<><div className="dash-tooltip">Show more link information</div></>} placement="top">
- <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div>
+ <p className="linkEditor-linkedTo">
+ Editing Link to: <b>{StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))}</b>
+ </p>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">Show more link information</div>
+ </>
+ }
+ placement="top">
+ <div className="linkEditor-downArrow">
+ <FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} />
+ </div>
</Tooltip>
</div>
- {this.showInfo ? <div className="linkEditor-moreInfo">
- <div>{this.props.linkDoc.author ? <div> <b>Author:</b> {this.props.linkDoc.author}</div> : null}</div>
- <div>{this.props.linkDoc.creationDate ? <div> <b>Creation Date:</b>
- {DateCast(this.props.linkDoc.creationDate).toString()}</div> : null}</div>
- </div> : null}
+ {this.showInfo ? (
+ <div className="linkEditor-moreInfo">
+ <div>
+ {this.props.linkDoc.author ? (
+ <div>
+ {' '}
+ <b>Author:</b> {StrCast(this.props.linkDoc.author)}
+ </div>
+ ) : null}
+ </div>
+ <div>
+ {this.props.linkDoc.creationDate ? (
+ <div>
+ {' '}
+ <b>Creation Date:</b>
+ {DateCast(this.props.linkDoc.creationDate).toString()}
+ </div>
+ ) : null}
+ </div>
+ </div>
+ ) : null}
{this.editDescription}
{this.editRelationship}
+ {this.editZoomFollow}
{this.followingDropdown}
</div>
-
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 19c6463d3..77c16a28f 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -45,8 +45,11 @@
}
.linkMenu-group-name {
- padding: 10px;
-
+ padding: 1px;
+ padding-left: 5px;
+ font-size: 10px;
+ font-style: italic;
+ font-weight: bold;
&:hover {
// p {
@@ -64,7 +67,7 @@
p {
width: 100%;
- line-height: 12px;
+ line-height: 1;
border-radius: 5px;
text-transform: capitalize;
}
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 53fe3f682..0096a58bd 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,18 +1,19 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { LinkManager } from "../../util/LinkManager";
-import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
-import { DocumentView, DocumentViewSharedProps } from "../nodes/DocumentView";
-import { LinkDocPreview } from "../nodes/LinkDocPreview";
-import { LinkEditor } from "./LinkEditor";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { LinkManager } from '../../util/LinkManager';
+import { DocumentView } from '../nodes/DocumentView';
+import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { LinkEditor } from './LinkEditor';
import './LinkMenu.scss';
-import { LinkMenuGroup } from "./LinkMenuGroup";
-import React = require("react");
+import { LinkMenuGroup } from './LinkMenuGroup';
+import React = require('react');
interface Props {
docView: DocumentView;
- changeFlyout: () => void;
+ position?: { x?: number; y?: number };
+ itemHandler?: (doc: Doc) => void;
+ clearLinkEditor: () => void;
}
/**
@@ -20,24 +21,32 @@ interface Props {
*/
@observer
export class LinkMenu extends React.Component<Props> {
- private _editorRef = React.createRef<HTMLDivElement>();
+ _editorRef = React.createRef<HTMLDivElement>();
@observable _editingLink?: Doc;
@observable _linkMenuRef = React.createRef<HTMLDivElement>();
@computed get position() {
- return ((dv) => ({ x: dv?.left || 0, y: dv?.top || 0, r: dv?.right || 0, b: dv?.bottom || 0 }))(this.props.docView.getBounds());
+ return this.props.position ?? (dv => ({ x: dv?.left || 0, y: (dv?.bottom || 0) + 15 }))(this.props.docView.getBounds());
}
- componentDidMount() { document.addEventListener("pointerdown", this.onPointerDown); }
- componentWillUnmount() { document.removeEventListener("pointerdown", this.onPointerDown); }
+ clear = action(() => {
+ this.props.clearLinkEditor();
+ this._editingLink = undefined;
+ });
- onPointerDown = (e: PointerEvent) => {
+ componentDidMount() {
+ document.addEventListener('pointerdown', this.onPointerDown, true);
+ }
+ componentWillUnmount() {
+ document.removeEventListener('pointerdown', this.onPointerDown, true);
+ }
+
+ onPointerDown = action((e: PointerEvent) => {
LinkDocPreview.Clear();
- if (!this._linkMenuRef.current?.contains(e.target as any) &&
- !this._editorRef.current?.contains(e.target as any)) {
- DocumentLinksButton.ClearLinkEditor();
+ if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) {
+ this.clear();
}
- }
+ });
/**
* maps each link to a JSX element to be rendered
@@ -45,30 +54,34 @@ export class LinkMenu extends React.Component<Props> {
* @returns list of link JSX elements if there at least one linked element
*/
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
- const linkItems = Array.from(groups.entries()).map(group =>
+ const linkItems = Array.from(groups.entries()).map(group => (
<LinkMenuGroup
key={group[0]}
+ itemHandler={this.props.itemHandler}
docView={this.props.docView}
sourceDoc={this.props.docView.props.Document}
group={group[1]}
groupType={group[0]}
- showEditor={action(linkDoc => this._editingLink = linkDoc)} />);
+ clearLinkEditor={this.clear}
+ showEditor={action(linkDoc => (this._editingLink = linkDoc))}
+ />
+ ));
- return linkItems.length ? linkItems : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
- }
+ return linkItems.length ? linkItems : this.props.position ? [<></>] : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
+ };
render() {
const sourceDoc = this.props.docView.props.Document;
- return <div className="linkMenu" ref={this._linkMenuRef}
- style={{ left: this.position.x, top: this.props.docView.topMost ? undefined : this.position.b + 15, bottom: this.props.docView.topMost ? 20 : undefined }}
- >
- {this._editingLink ?
- <div className="linkMenu-listEditor">
- <LinkEditor sourceDoc={sourceDoc} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
- </div> :
- <div className="linkMenu-list" >
- {this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}
- </div>}
- </div>;
+ return (
+ <div className="linkMenu" ref={this._linkMenuRef} style={{ left: this.position.x, top: this.props.docView.topMost ? undefined : this.position.y, bottom: this.props.docView.topMost ? 20 : undefined }}>
+ {this._editingLink ? (
+ <div className="linkMenu-listEditor">
+ <LinkEditor sourceDoc={sourceDoc} linkDoc={this._editingLink} showLinks={action(() => (this._editingLink = undefined))} />
+ </div>
+ ) : (
+ <div className="linkMenu-list">{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}</div>
+ )}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 03377ad4e..fa6a2f506 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,4 +1,5 @@
import { observer } from "mobx-react";
+import { observable, action } from "mobx";
import { Doc, StrListCast } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { Cast } from "../../../fields/Types";
@@ -12,8 +13,10 @@ interface LinkMenuGroupProps {
sourceDoc: Doc;
group: Doc[];
groupType: string;
+ clearLinkEditor: () => void;
showEditor: (linkDoc: Doc) => void;
docView: DocumentView;
+ itemHandler?: (doc: Doc) => void;
}
@observer
@@ -36,6 +39,8 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return color;
}
+ @observable _collapsed = false;
+
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -43,11 +48,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
if (destination && this.props.sourceDoc) {
return <LinkMenuItem key={linkDoc[Id]}
+ itemHandler={this.props.itemHandler}
groupType={this.props.groupType}
docView={this.props.docView}
linkDoc={linkDoc}
sourceDoc={this.props.sourceDoc}
destinationDoc={destination}
+ clearLinkEditor={this.props.clearLinkEditor}
showEditor={this.props.showEditor}
menuRef={this._menuRef} />;
}
@@ -55,12 +62,12 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
- <div className="linkMenu-group-name" style={{ background: this.getBackgroundColor() }}>
+ <div className="linkMenu-group-name" onClick={action(() => this._collapsed = !this._collapsed)} style={{ background: this.getBackgroundColor() }}>
<p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"}> {this.props.groupType}:</p>
</div>
- <div className="linkMenu-group-wrapper">
+ {this._collapsed ? (null) : <div className="linkMenu-group-wrapper">
{groupItems}
- </div>
+ </div>}
</div >
);
}
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 90722daf9..8333aa374 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -9,8 +9,8 @@
padding-left: 6.5px;
padding-right: 2px;
- padding-top: 6.5px;
- padding-bottom: 6.5px;
+ padding-top: 1px;
+ padding-bottom: 1px;
background-color: white;
@@ -18,10 +18,12 @@
.linkMenu-name {
position: relative;
width: auto;
+ align-items: center;
+ display: flex;
.linkMenu-text {
- padding: 4px 2px;
+ // padding: 4px 2px;
//display: inline;
.linkMenu-source-title {
@@ -37,6 +39,8 @@
.linkMenu-title-wrapper {
display: flex;
+ align-items: center;
+ min-height: 20px;
.destination-icon-wrapper {
//border: 0.5px solid rgb(161, 161, 161);
@@ -56,7 +60,8 @@
.linkMenu-destination-title {
text-decoration: none;
color: #4476F7;
- font-size: 16px;
+ font-size: 13px;
+ line-height: 0.9;
padding-bottom: 2px;
padding-right: 4px;
margin-right: 4px;
@@ -77,6 +82,7 @@
font-style: italic;
color: rgb(95, 97, 102);
font-size: 9px;
+ line-height: 0.9;
margin-left: 20px;
max-width: 125px;
height: auto;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 96cc6d600..ed856a4ab 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,25 +1,23 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, observable, runInAction } from 'mobx';
-import { observer } from "mobx-react";
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { Cast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
-import { emptyFunction, setupMoveUpEvents, returnFalse } from '../../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
import { Hypothesis } from '../../util/HypothesisUtils';
+import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { undoBatch } from '../../util/UndoManager';
-import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import './LinkMenuItem.scss';
-import React = require("react");
-import { setup } from 'mocha';
-
+import React = require('react');
interface LinkMenuItemProps {
groupType: string;
@@ -27,8 +25,10 @@ interface LinkMenuItemProps {
docView: DocumentView;
sourceDoc: Doc;
destinationDoc: Doc;
+ clearLinkEditor: () => void;
showEditor: (linkDoc: Doc) => void;
menuRef: React.Ref<HTMLDivElement>;
+ itemHandler?: (doc: Doc) => void;
}
// drag links and drop link targets (aliasing them if needed)
@@ -50,22 +50,21 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
};
const containingView = docView.props.ContainingCollectionView;
const finishDrag = (e: DragManager.DragCompleteEvent) =>
- e.docDragData && (e.docDragData.droppedDocuments =
- dragData.draggedDocuments.reduce((droppedDocs, d) => {
- const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
- if (dvs.length) {
- dvs.forEach(dv => droppedDocs.push(dv.props.Document));
- } else {
- droppedDocs.push(Doc.MakeAlias(d));
- }
- return droppedDocs;
- }, [] as Doc[]));
+ e.docDragData &&
+ (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => {
+ const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
+ if (dvs.length) {
+ dvs.forEach(dv => droppedDocs.push(dv.props.Document));
+ } else {
+ droppedDocs.push(Doc.MakeAlias(d));
+ }
+ return droppedDocs;
+ }, [] as Doc[]));
DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
}
}
-
@observer
export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
@@ -74,125 +73,206 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
_buttonRef = React.createRef<HTMLDivElement>();
@observable private _showMore: boolean = false;
- @action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
+ @action toggleShowMore(e: React.PointerEvent) {
+ e.stopPropagation();
+ this._showMore = !this._showMore;
+ }
onEdit = (e: React.PointerEvent): void => {
LinkManager.currentLink = this.props.linkDoc;
- setupMoveUpEvents(this, e, e => {
- const dragData = new DragManager.DocumentDragData([this.props.linkDoc], "alias");
- dragData.removeDropProperties = ["hidden"];
- DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
- return true;
- }, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ e => {
+ const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'alias');
+ dragData.removeDropProperties = ['hidden'];
+ DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
+ return true;
+ },
+ emptyFunction,
+ () => this.props.showEditor(this.props.linkDoc)
+ );
+ };
onLinkButtonDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e,
+ setupMoveUpEvents(
+ this,
+ e,
e => {
const eleClone: any = this._drag.current!.cloneNode(true);
eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
StartLinkTargetsDrag(eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
- DocumentLinksButton.ClearLinkEditor();
+ this.props.clearLinkEditor();
return true;
},
emptyFunction,
() => {
- DocumentLinksButton.ClearLinkEditor();
- LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
- });
- }
+ this.props.clearLinkEditor();
+ if (this.props.itemHandler) {
+ this.props.itemHandler?.(this.props.linkDoc);
+ } else {
+ const focusDoc =
+ Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc
+ ? Cast(this.props.linkDoc.anchor1, Doc, null)
+ : Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc
+ ? Cast(this.props.linkDoc.anchor12, Doc, null)
+ : undefined;
+
+ if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, true);
+ LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
+ }
+ }
+ );
+ };
deleteLink = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => {
- this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
- LinkManager.Instance.deleteLink(this.props.linkDoc);
- })));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoBatch(
+ action(() => {
+ this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+ LinkManager.Instance.deleteLink(this.props.linkDoc);
+ })
+ )
+ );
+ };
autoMove = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)));
- }
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove))));
+ };
showLink = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)));
- }
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay))));
+ };
showAnchor = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.hidden = !this.props.linkDoc.hidden)));
- }
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden))));
+ };
render() {
const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp;
- const title = StrCast(this.props.destinationDoc.title).length > 18 ?
- StrCast(this.props.destinationDoc.title).substr(0, 14) + "..." : this.props.destinationDoc.title;
+ const title = StrCast(this.props.destinationDoc.title).length > 18 ? StrCast(this.props.destinationDoc.title).substr(0, 14) + '...' : this.props.destinationDoc.title;
// ...
// from anika to bob: here's where the text that is specifically linked would show up (linkDoc.storedText)
// ...
- const source = this.props.sourceDoc.type === DocumentType.RTF ? this.props.linkDoc.storedText ?
- StrCast(this.props.linkDoc.storedText).length > 17 ?
- StrCast(this.props.linkDoc.storedText).substr(0, 18)
- : this.props.linkDoc.storedText : undefined : undefined;
+ const source =
+ this.props.sourceDoc.type === DocumentType.RTF
+ ? this.props.linkDoc.storedText
+ ? StrCast(this.props.linkDoc.storedText).length > 17
+ ? StrCast(this.props.linkDoc.storedText).substr(0, 18)
+ : this.props.linkDoc.storedText
+ : undefined
+ : undefined;
return (
<div className="linkMenu-item">
- <div className={"linkMenu-item-content expand-two"}>
-
- <div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
+ <div className={'linkMenu-item-content expand-two'}>
+ <div
+ ref={this._drag}
+ className="linkMenu-name" //title="drag to view target. click to customize."
onPointerLeave={LinkDocPreview.Clear}
- onPointerEnter={e => this.props.linkDoc && LinkDocPreview.SetLinkInfo({
- docProps: this.props.docView.props,
- linkSrc: this.props.sourceDoc,
- linkDoc: this.props.linkDoc,
- showHeader: false,
- location: [e.clientX, e.clientY + 20]
- })}
+ onPointerEnter={e =>
+ this.props.linkDoc &&
+ LinkDocPreview.SetLinkInfo({
+ docProps: this.props.docView.props,
+ linkSrc: this.props.sourceDoc,
+ linkDoc: this.props.linkDoc,
+ showHeader: false,
+ location: [e.clientX, e.clientY + 20],
+ })
+ }
onPointerDown={this.onLinkButtonDown}>
-
<div className="linkMenu-text">
- {source ? <p className="linkMenu-source-title"> <b>Source: {source}</b></p> : null}
+ {source ? (
+ <p className="linkMenu-source-title">
+ {' '}
+ <b>Source: {StrCast(source)}</b>
+ </p>
+ ) : null}
<div className="linkMenu-title-wrapper">
- <div className="destination-icon-wrapper" >
+ <div className="destination-icon-wrapper">
<FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
</div>
<p className="linkMenu-destination-title">
- {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? "Annotation in" : ""} {title}
+ {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
</p>
</div>
- {!this.props.linkDoc.description ? (null) : <p className="linkMenu-description">{StrCast(this.props.linkDoc.description)}</p>}
+ {!this.props.linkDoc.description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.description)}</p>}
</div>
- <div className="linkMenu-item-buttons" ref={this._buttonRef} >
-
- <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Link Anchor" : "Hide Link Anchor"}</div></>}>
- <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={"eye"} size="sm" /></div>
+ <div className="linkMenu-item-buttons" ref={this._buttonRef}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}</div>
+ </>
+ }>
+ <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? '' : '#4476f7' /* $medium-blue */ }} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={'eye'} size="sm" />
+ </div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? "Hide Link Line" : "Show Link Line"}</div></>}>
- <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : this.props.linkDoc.linkDisplay ? "#4476f7"/* $medium-blue */ : "" }} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={"project-diagram"} size="sm" /></div>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}</div>
+ </>
+ }>
+ <div
+ className="button"
+ ref={this._editRef}
+ style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkDisplay ? '#4476f7' /* $medium-blue */ : '' }}
+ onPointerDown={this.showLink}
+ onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={'project-diagram'} size="sm" />
+ </div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}</div></>}>
- <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : !this.props.linkDoc.linkAutoMove ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={"play"} size="sm" /></div>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}</div>
+ </>
+ }>
+ <div
+ className="button"
+ ref={this._editRef}
+ style={{ background: this.props.linkDoc.hidden ? 'gray' : !this.props.linkDoc.linkAutoMove ? '' : '#4476f7' /* $medium-blue */ }}
+ onPointerDown={this.autoMove}
+ onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={'play'} size="sm" />
+ </div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">Edit Link</div>
+ </>
+ }>
<div className="button" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
+ <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" />
+ </div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">Delete Link</div>
+ </>
+ }>
<div className="button" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
+ <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
+ </div>
</Tooltip>
</div>
</div>
</div>
-
- </div >
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index c8be9069c..0bcb68f82 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,17 +1,16 @@
import { action, observable } from 'mobx';
-import { observer } from "mobx-react";
+import { observer } from 'mobx-react';
import { EditorView } from 'prosemirror-view';
+import { Doc } from '../../../fields/Doc';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { SearchBox } from '../search/SearchBox';
import { DefaultStyleProvider } from '../StyleProvider';
import './LinkPopup.scss';
-import React = require("react");
-import { Doc, Opt } from '../../../fields/Doc';
+import React = require('react');
interface LinkPopupProps {
showPopup: boolean;
@@ -23,33 +22,30 @@ interface LinkPopupProps {
}
/**
- * Popup component for creating links from text to Dash documents
+ * Popup component for creating links from text to Dash documents
*/
@observer
export class LinkPopup extends React.Component<LinkPopupProps> {
- @observable private linkURL: string = "";
+ @observable private linkURL: string = '';
@observable public view?: EditorView;
-
-
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
- }
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
+ };
@action
onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.linkURL = e.target.value;
- }
-
+ };
getPWidth = () => 500;
getPHeight = () => 500;
render() {
- const popupVisibility = this.props.showPopup ? "block" : "none";
+ const popupVisibility = this.props.showPopup ? 'block' : 'none';
const linkDoc = this.props.linkFrom ? this.props.linkFrom : undefined;
return (
<div className="linkPopup-container" style={{ display: popupVisibility }}>
@@ -67,9 +63,9 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
<input defaultValue={""} autoComplete="off" type="text" placeholder="Search for Document..." id="search-input"
className="linkPopup-searchBox searchBox-input" /> */}
- <SearchBox
- Document={CurrentUserUtils.MySearchPanelDoc}
- DataDoc={CurrentUserUtils.MySearchPanelDoc}
+ <SearchBox
+ Document={Doc.MySearcher}
+ DataDoc={Doc.MySearcher}
linkFrom={linkDoc}
linkSearch={true}
fieldKey="data"
@@ -83,7 +79,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
pinToPres={emptyFunction}
rootSelected={returnTrue}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
PanelWidth={this.getPWidth}
@@ -97,9 +92,10 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
+ ContainingCollectionDoc={undefined}
+ />
</div>
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index a6494e540..d40537776 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,222 +1,214 @@
@import "../global/globalCssVariables.scss";
-
-.audiobox-container,
-.audiobox-container-interactive {
+.audiobox-container {
width: 100%;
height: 100%;
position: inherit;
display: flex;
position: relative;
cursor: default;
+}
+
+.audiobox-recorder {
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
- .audiobox-buttons {
+ .audiobox-dictation {
+ width: 40px;
+ background: $medium-gray;
+ color: $dark-gray;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ color: $black;
+ }
+ }
+
+ .audiobox-start-record {
+ color: $white;
+ background: $dark-gray;
display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: $body-text;
width: 100%;
+ height: 100%;
+ gap: 5px;
+
+ &:hover {
+ background: $black;
+ }
+ }
+
+ .recording-controls {
+ display: flex;
+ flex-direction: row;
align-items: center;
+ justify-content: center;
+ gap: 5px;
+ width: 100%;
height: 100%;
+ background: $dark-gray;
+ color: white;
- .audiobox-dictation {
- position: relative;
+ .record-timecode {
+ font-size: $large-header;
+ }
+
+ .record-button {
+ cursor: pointer;
width: 30px;
- height: 100%;
+ height: 30px;
+ border-radius: 50%;
+ background: $dark-gray;
+ display: flex;
align-items: center;
- display: inherit;
- background: $medium-gray;
- left: 0px;
- color: $dark-gray;
+ justify-content: center;
+
+ svg {
+ width: 15px;
+ }
&:hover {
- color: $black;
- cursor: pointer;
+ background: $black;
}
}
}
+}
- .audiobox-control,
- .audiobox-control-interactive {
- top: 0;
- max-height: 32px;
- width: 100%;
- display: inline-block;
- pointer-events: none;
- }
-
- .audiobox-control-interactive {
- pointer-events: all;
- }
+.audiobox-file {
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background: $dark-gray;
+ width: 100%;
+ height: 100%;
+ color: $white;
- .audiobox-record-interactive,
- .audiobox-record {
- pointer-events: all;
+ .audiobox-button {
+ margin: 2.5px;
cursor: pointer;
- width: 100%;
- height: 100%;
- position: relative;
+ width: 25px;
+ height: 25px;
+ border-radius: 50%;
+ background: $dark-gray;
display: flex;
- flex-direction: row;
align-items: center;
justify-content: center;
- gap: 10px;
- color: white;
- font-weight: bold;
+
+ svg {
+ width: 15px;
+ }
+
+ &:hover {
+ background: $black;
+ }
+ }
+
+ svg {
+ width: 10px;
+ }
+
+ input[type="range"] {
+ width: 50px;
+ -webkit-appearance: none;
+ background: none;
+ margin: 5px;
}
- .audiobox-record {
- pointer-events: none;
+ input[type="range"]:focus {
+ outline: none;
}
- .recording {
- margin-top: auto;
- margin-bottom: auto;
+ input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
- height: 100%;
- position: relative;
- padding-right: 5px;
+ height: 6px;
+ cursor: pointer;
+ box-shadow: 0;
+ background: $light-gray;
+ border-radius: 3px;
+ }
+
+ input[type="range"]::-webkit-slider-thumb {
+ box-shadow: 0;
+ border: 0;
+ height: 10px;
+ width: 10px;
+ border-radius: 10px;
+ background: $medium-blue;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -2px;
+ }
+
+ .audiobox-controls {
display: flex;
flex-direction: row;
- justify-content: center;
+ justify-content: space-between;
align-items: center;
- gap: 7px;
- background-color: $medium-blue;
- padding: 10px;
+ width: 100%;
+ height: 30px;
- .time {
- position: relative;
- height: 100%;
- width: 100%;
- font-size: 16px;
- text-align: center;
+ .controls-left {
display: flex;
- justify-content: center;
- align-items: center;
- font-weight: bold;
+ flex-direction: row;
}
- .buttons {
- cursor: pointer;
- position: relative;
- margin-top: auto;
- margin-bottom: auto;
- width: 25px;
- width: 25px;
- padding: 5px;
- color: $dark-gray;
+ .controls-right {
+ display: flex;
+ flex-direction: row;
- &:hover {
- color: $black;
+ .audiobox-button {
+ width: 15px;
+ height: 15px;
+ margin: 0;
+
+ svg {
+ width: 10px;
+ }
}
}
}
- .audiobox-controls {
+ .audiobox-playback {
width: 100%;
height: 100%;
- position: relative;
- display: flex;
- background: $dark-gray;
+ background: $white;
- .audiobox-dictation {
+ .audiobox-timeline {
+ height: calc(100% - 50px);
+ width: 100%;
+ background: $white;
position: absolute;
- width: 40px;
- height: 100%;
- align-items: center;
- display: inherit;
- background: $medium-gray;
- left: 0px;
}
- .audiobox-player {
- margin-top: auto;
- margin-bottom: auto;
+ .audiobox-timeline > div {
width: 100%;
- position: relative;
- padding-right: 5px;
- display: flex;
- flex-direction: column;
- justify-content: center;
-
- .audiobox-buttons {
- position: relative;
- margin-top: auto;
- margin-bottom: auto;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- background-color: $dark-gray;
- color: $white;
- display: flex;
- align-items: center;
- justify-content: center;
- left: 5px;
-
- &:hover {
- background-color: $black;
- }
-
- svg {
- width: 100%;
- position: absolute;
- border-width: "thin";
- border-color: "white";
- }
- }
-
- .audiobox-dictation {
- position: relative;
- margin-top: auto;
- margin-bottom: auto;
- width: 25px;
- align-items: center;
- display: inherit;
- background: $medium-gray;
- }
-
- .audiobox-timeline {
- position: absolute;
- width: 100%;
- z-index: 1000;
- overflow: hidden;
- border-right: 5px solid black;
- }
-
- .audioBox-total-time,
- .audioBox-current-time {
- position: absolute;
- font-size: $small-text;
- top: 100%;
- color: $white;
- }
-
- .audioBox-current-time {
- left: 42px;
- }
-
- .audioBox-total-time {
- right: 2px;
- }
+ height: 100%;
}
}
-}
-@media only screen and (max-device-width: 480px) {
- .audiobox-dictation {
- font-size: 5em;
+ .audiobox-timecodes {
display: flex;
- width: 100;
- justify-content: center;
- flex-direction: column;
+ flex-direction: row;
+ justify-content: space-between;
align-items: center;
- }
-
- .audiobox-container .audiobox-record,
- .audiobox-container-interactive .audiobox-record {
- font-size: 3em;
- }
+ width: 100%;
+ height: 20px;
+ padding: 3px;
+ font-size: $small-text;
- .audiobox-container .audiobox-controls .audiobox-player .audiobox-buttons,
- .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation,
- .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-buttons {
- width: 70px;
+ .bottom-controls-middle {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 93377f1dc..8437736ae 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,136 +1,130 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- action,
- computed,
- IReactionDisposer,
- observable,
- reaction,
- runInAction
-} from "mobx";
-import { observer } from "mobx-react";
-import { DateField } from "../../../fields/DateField";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, NumCast } from "../../../fields/Types";
-import { AudioField, nullAudio } from "../../../fields/URLField";
-import { emptyFunction, formatTime } from "../../../Utils";
-import { DocUtils } from "../../documents/Documents";
-import { Networking } from "../../Network";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { SnappingManager } from "../../util/SnappingManager";
-import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import {
- ViewBoxAnnotatableComponent,
- ViewBoxAnnotatableProps
-} from "../DocComponent";
-import { Colors } from "../global/globalEnums";
-import "./AudioBox.scss";
-import { FieldView, FieldViewProps } from "./FieldView";
-import { LinkDocPreview } from "./LinkDocPreview";
-
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { DateField } from '../../../fields/DateField';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { ComputedField } from '../../../fields/ScriptField';
+import { Cast, DateCast, NumCast } from '../../../fields/Types';
+import { AudioField, nullAudio } from '../../../fields/URLField';
+import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { Networking } from '../../Network';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
+import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import './AudioBox.scss';
+import { FieldView, FieldViewProps } from './FieldView';
+
+/**
+ * AudioBox
+ * Main component: AudioBox.tsx
+ * Supporting Components: CollectionStackedTimeline, AudioWaveform
+ *
+ * AudioBox is a node that supports the recording and playback of audio files in Dash.
+ * When an audio file is importeed into Dash, it is immediately rendered as an AudioBox document.
+ * When a blank AudioBox node is created in Dash, audio recording controls are displayed and the user can start a recording which can be paused or stopped, and can use dictation to create a text transcript.
+ * Recording is done using the MediaDevices API to access the user's device microphone (see recordAudioAnnotation below)
+ * CollectionStackedTimeline handles AudioBox and VideoBox shared behavior, but AudioBox handles playing, pausing, etc because it contains <audio> element
+ * User can trim audio: nondestructive, just sets new bounds for playback and rendering timelin
+ */
+
+// used as a wrapper class for MediaStream from MediaDevices API
declare class MediaRecorder {
constructor(e: any); // whatever MediaRecorder has
}
+
+enum media_state {
+ PendingRecording = 'pendingRecording',
+ Recording = 'recording',
+ Paused = 'paused',
+ Playing = 'playing',
+}
+
@observer
export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(AudioBox, fieldKey);
}
public static Enabled = false;
- static playheadWidth = 40; // width of playhead
- static heightPercent = 75; // height of timeline in percent of height of audioBox.
- static Instance: AudioBox;
+ static topControlsHeight = 30; // height of upper controls above timeline
+ static bottomControlsHeight = 20; // height of lower controls below timeline
+
+ _dropDisposer?: DragManager.DragDropDisposer;
_disposers: { [name: string]: IReactionDisposer } = {};
- _ele: HTMLAudioElement | null = null;
- _stackedTimeline = React.createRef<CollectionStackedTimeline>();
- _recorder: any;
+ _ele: HTMLAudioElement | null = null; // <audio> ref
+ _recorder: any; // MediaRecorder
_recordStart = 0;
- _pauseStart = 0;
- _pauseEnd = 0;
+ _pauseStart = 0; // time when recording is paused (used to keep track of recording timecodes)
_pausedTime = 0;
- _stream: MediaStream | undefined;
- _start: number = 0;
- _play: any = null;
- _ended: boolean = false;
-
- @observable static _scrubTime = 0;
- @observable _markerEnd: number = 0;
- @observable _position: number = 0;
- @observable _waveHeight: Opt<number> = NumCast(this.layoutDoc._height);
- @observable _paused: boolean = false;
- @observable _trimming: boolean = false;
- @observable _trimStart: number = NumCast(this.layoutDoc.clipStart) ? NumCast(this.layoutDoc.clipStart) : 0;
- @observable _trimEnd: number = NumCast(this.layoutDoc.clipEnd) ? NumCast(this.layoutDoc.clipEnd)
- : this.duration;
-
- @computed get mediaState():
- | undefined
- | "pendingRecording"
- | "recording"
- | "paused"
- | "playing" {
- return this.dataDoc.mediaState as
- | undefined
- | "pendingRecording"
- | "recording"
- | "paused"
- | "playing";
- }
- set mediaState(value) {
- this.dataDoc.mediaState = value;
- }
- public static SetScrubTime = action((timeInMillisFrom1970: number) => {
- AudioBox._scrubTime = 0;
- AudioBox._scrubTime = timeInMillisFrom1970;
- });
+ _stream: MediaStream | undefined; // passed to MediaRecorder, records device input audio
+ _play: any = null; // timeout for playback
+
+ @observable _stackedTimeline: any; // CollectionStackedTimeline ref
+ @observable _finished: boolean = false; // has playback reached end of clip
+ @observable _volume: number = 1;
+ @observable _muted: boolean = false;
+ @observable _paused: boolean = false; // is recording paused
+ // @observable rawDuration: number = 0; // computed from the length of the audio element when loaded
@computed get recordingStart() {
- return Cast(
- this.dataDoc[this.props.fieldKey + "-recordingStart"],
- DateField
- )?.date.getTime();
+ return DateCast(this.dataDoc[this.fieldKey + '-recordingStart'])?.date.getTime();
}
- @computed get duration() {
+ @computed get rawDuration() {
return NumCast(this.dataDoc[`${this.fieldKey}-duration`]);
+ } // bcz: shouldn't be needed since it's computed from audio element
+ // mehek: not 100% sure but i think due to the order in which things are loaded this is necessary ^^
+ // if you get rid of it and set the value to 0 the timeline and waveform will set their bounds incorrectly
+
+ @computed get miniPlayer() {
+ return this.props.PanelHeight() < 50;
+ } // used to collapse timeline when node is shrunk
+ @computed get links() {
+ return DocListCast(this.dataDoc.links);
}
- @computed get trimDuration() {
- return this._trimming && this._trimEnd ? this.duration : this._trimEnd - this._trimStart;
+ @computed get mediaState() {
+ return this.dataDoc.mediaState as media_state;
}
- @computed get anchorDocs() {
- return DocListCast(this.dataDoc[this.annotationKey]);
+ @computed get path() {
+ // returns the path of the audio file
+ const path = Cast(this.props.Document[this.fieldKey], AudioField, null)?.url.href || '';
+ return path === nullAudio ? '' : path;
}
- @computed get links() {
- return DocListCast(this.dataDoc.links);
+ set mediaState(value) {
+ this.dataDoc.mediaState = value;
}
- @computed get pauseTime() {
- return this._pauseEnd - this._pauseStart;
- } // total time paused to update the correct time
- @computed get heightPercent() {
- return AudioBox.heightPercent;
+
+ @computed get timeline() {
+ return this._stackedTimeline;
+ } // returns CollectionStackedTimeline ref
+
+ componentWillUnmount() {
+ this.removeCurrentlyPlaying();
+ this._dropDisposer?.();
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+
+ this.mediaState === media_state.Recording && this.stopRecording();
}
- constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) {
- super(props);
- AudioBox.Instance = this;
+ @action
+ componentDidMount() {
+ this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
- if (this.duration === undefined) {
- runInAction(
- () =>
- (this.Document[this.fieldKey + "-duration"] = this.Document.duration)
- );
+ if (this.path) {
+ this.mediaState = media_state.Paused;
+ this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
+ } else {
+ this.mediaState = undefined as any as media_state;
}
}
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- const linkTime =
- this._stackedTimeline.current?.anchorStart(la2) ||
- this._stackedTimeline.current?.anchorStart(la1) ||
- 0;
+ const linkTime = this.timeline?.anchorStart(la2) || this.timeline?.anchorStart(la1) || 0;
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
@@ -144,416 +138,524 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.rootDoc,
this.dataDoc,
this.annotationKey,
- "_timecodeToShow" /* audioStart */,
- "_timecodeToHide" /* audioEnd */,
- this._ele?.currentTime ||
- Cast(this.props.Document._currentTimecode, "number", null) ||
- (this.mediaState === "recording"
- ? (Date.now() - (this.recordingStart || 0)) / 1000
- : undefined)
+ '_timecodeToShow' /* audioStart */,
+ '_timecodeToHide' /* audioEnd */,
+ this._ele?.currentTime || Cast(this.props.Document._currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)
) || this.rootDoc
);
- }
-
- componentWillUnmount() {
- Object.values(this._disposers).forEach((disposer) => disposer?.());
- const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
- }
-
- @action
- componentDidMount() {
- this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
-
- this.mediaState = this.path ? "paused" : undefined;
-
- this.layoutDoc.clipStart = this.layoutDoc.clipStart ? this.layoutDoc.clipStart : 0;
- this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? this.layoutDoc.clipEnd : this.duration ? this.duration : undefined;
-
- this.path && this.setAnchorTime(NumCast(this.layoutDoc.clipStart));
- this.path && this.timecodeChanged();
-
- this._disposers.triggerAudio = reaction(
- () =>
- !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1
- ? NumCast(this.Document._triggerAudio, null)
- : undefined,
- (start) =>
- start !== undefined &&
- setTimeout(() => {
- this.playFrom(start);
- setTimeout(() => {
- this.Document._currentTimecode = start;
- this.Document._triggerAudio = undefined;
- }, 10);
- }), // wait for mainCont and try again to play
- { fireImmediately: true }
- );
+ };
- this._disposers.audioStop = reaction(
- () =>
- this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo
- ? Cast(this.Document._audioStop, "number", null)
- : undefined,
- (audioStop) =>
- audioStop !== undefined &&
- setTimeout(() => {
- this.Pause();
- setTimeout(() => (this.Document._audioStop = undefined), 10);
- }), // wait for mainCont and try again to play
- { fireImmediately: true }
- );
- }
-
- // for updating the timecode
+ // updates timecode and shows it in timeline, follows links at time
@action
timecodeChanged = () => {
- const htmlEle = this._ele;
- if (this.mediaState !== "recording" && htmlEle) {
- htmlEle.duration &&
- htmlEle.duration !== Infinity &&
- runInAction(
- () => (this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration)
- );
- this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? Math.min(this.duration, NumCast(this.layoutDoc.clipEnd)) : this.duration;
- this._trimEnd = this._trimEnd ? Math.min(this.duration, this._trimEnd) : this.duration;
+ if (this.mediaState !== media_state.Recording && this._ele) {
this.links
- .map((l) => this.getLinkData(l))
+ .map(l => this.getLinkData(l))
.forEach(({ la1, la2, linkTime }) => {
- if (
- linkTime > NumCast(this.layoutDoc._currentTimecode) &&
- linkTime < htmlEle.currentTime
- ) {
+ if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < this._ele!.currentTime) {
Doc.linkFollowHighlight(la1);
}
});
- this.layoutDoc._currentTimecode = htmlEle.currentTime;
-
+ this.layoutDoc._currentTimecode = this._ele.currentTime;
+ this.timeline?.scrollToTime(NumCast(this.layoutDoc._currentTimecode));
}
- }
+ };
- // pause play back
- Pause = action(() => {
- this._ele!.pause();
- this.mediaState = "paused";
- });
-
- // play audio for documents created during recording
- playFromTime = (absoluteTime: number) => {
- this.recordingStart &&
- this.playFrom((absoluteTime - this.recordingStart) / 1000);
- }
-
- // play back the audio from time
+ // play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range
@action
- playFrom = (seekTimeInSeconds: number, endTime: number = this._trimEnd, fullPlay: boolean = false) => {
- clearTimeout(this._play);
+ playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
+ clearTimeout(this._play); // abort any previous clip ending
if (Number.isNaN(this._ele?.duration)) {
+ // audio element isn't loaded yet... wait 1/2 second and try again
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
- } else if (this._ele && AudioBox.Enabled) {
- if (seekTimeInSeconds < 0) {
- if (seekTimeInSeconds > -1) {
- setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000);
- } else {
- this.Pause();
- }
- } else if (this._trimStart <= endTime && seekTimeInSeconds <= this._trimEnd) {
- const start = Math.max(this._trimStart, seekTimeInSeconds);
- const end = Math.min(this._trimEnd, endTime);
+ } else if (this.timeline && this._ele && AudioBox.Enabled) {
+ // trimBounds override requested playback bounds
+ const end = Math.min(this.timeline.trimEnd, endTime ?? this.timeline.trimEnd);
+ const start = Math.max(this.timeline.trimStart, seekTimeInSeconds);
+ // checks if times are within clip range
+ if (seekTimeInSeconds >= 0 && this.timeline.trimStart <= end && seekTimeInSeconds <= this.timeline.trimEnd) {
this._ele.currentTime = start;
this._ele.play();
- runInAction(() => (this.mediaState = "playing"));
- if (endTime !== this.duration) {
- this._play = setTimeout(
- () => {
- this._ended = fullPlay ? true : this._ended;
- this.Pause();
- },
- (end - start) * 1000
- ); // use setTimeout to play a specific duration
- }
+ this.mediaState = media_state.Playing;
+ this.addCurrentlyPlaying();
+ this._play = setTimeout(() => {
+ // need to keep track of if end of clip is reached so on next play, clip restarts
+ if (fullPlay) this._finished = true;
+ // removes from currently playing if playback has reached end of range marker
+ else this.removeCurrentlyPlaying();
+ this.Pause();
+ }, (end - start) * 1000);
} else {
this.Pause();
}
}
- }
+ };
+
+ // removes from currently playing display
+ @action
+ removeCurrentlyPlaying = () => {
+ if (CollectionStackedTimeline.CurrentlyPlaying) {
+ const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc);
+ index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
+ }
+ };
+
+ // adds doc to currently playing display
+ @action
+ addCurrentlyPlaying = () => {
+ if (!CollectionStackedTimeline.CurrentlyPlaying) {
+ CollectionStackedTimeline.CurrentlyPlaying = [];
+ }
+ if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc) === -1) {
+ CollectionStackedTimeline.CurrentlyPlaying.push(this.layoutDoc);
+ }
+ };
// update the recording time
updateRecordTime = () => {
- if (this.mediaState === "recording") {
+ if (this.mediaState === media_state.Recording) {
setTimeout(this.updateRecordTime, 30);
- if (this._paused) {
- this._pausedTime += (new Date().getTime() - this._recordStart) / 1000;
- } else {
- this.layoutDoc._currentTimecode =
- (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+ if (!this._paused) {
+ this.layoutDoc._currentTimecode = (new Date().getTime() - this._recordStart - this._pausedTime) / 1000;
}
}
- }
+ };
// starts recording
recordAudioAnnotation = async () => {
this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this._recorder = new MediaRecorder(this._stream);
- this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(
- new Date()
- );
+ this.dataDoc[this.fieldKey + '-recordingStart'] = new DateField();
DocUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
- console.log("Data available", e);
const [{ result }] = await Networking.UploadFilesToServer(e.data);
- console.log("Data result", result);
if (!(result instanceof Error)) {
- this.props.Document[this.props.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
+ this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._recordStart = new Date().getTime();
- runInAction(() => (this.mediaState = "recording"));
- setTimeout(this.updateRecordTime, 0);
+ runInAction(() => (this.mediaState = media_state.Recording));
+ setTimeout(this.updateRecordTime);
this._recorder.start();
- setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour
- }
+ setTimeout(this.stopRecording, 60 * 60 * 1000); // stop after an hour
+ };
+
+ // stops recording
+ @action
+ stopRecording = () => {
+ if (this._recorder) {
+ this._recorder.stop();
+ this._recorder = undefined;
+ const now = new Date().getTime();
+ this._paused && (this._pausedTime += now - this._pauseStart);
+ this.dataDoc[this.fieldKey + '-duration'] = (now - this._recordStart - this._pausedTime) / 1000;
+ this.mediaState = media_state.Paused;
+ this._stream?.getAudioTracks()[0].stop();
+ const ind = DocUtils.ActiveRecordings.indexOf(this);
+ ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
+ }
+ };
// context menu
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
funcs.push({
- description:
- (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors",
- event: () => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
- icon: "expand-arrows-alt",
+ description: (this.layoutDoc.hideAnchors ? "Don't hide" : 'Hide') + ' anchors',
+ event: e => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
+ icon: 'expand-arrows-alt',
+ });
+ funcs.push({
+ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
+ event: e => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks),
+ icon: 'expand-arrows-alt',
});
funcs.push({
- description:
- (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") +
- " play when link is selected",
- event: () =>
- (this.layoutDoc.dontAutoPlayFollowedLinks =
- !this.layoutDoc.dontAutoPlayFollowedLinks),
- icon: "expand-arrows-alt",
+ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
+ event: e => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+ icon: 'expand-arrows-alt',
});
funcs.push({
- description:
- (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") +
- " anchors onClick",
- event: () =>
- (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
- icon: "expand-arrows-alt",
+ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto" : 'Auto') + ' play anchors onClick',
+ event: e => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
+ icon: 'expand-arrows-alt',
});
ContextMenu.Instance?.addItem({
- description: "Options...",
+ description: 'Options...',
subitems: funcs,
- icon: "asterisk",
+ icon: 'asterisk',
});
- }
-
- // stops the recording
- stopRecording = action(() => {
- this._recorder.stop();
- this._recorder = undefined;
- this.dataDoc[this.fieldKey + "-duration"] =
- (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
- this.mediaState = "paused";
- this._trimEnd = this.duration;
- this.layoutDoc.clipStart = 0;
- this.layoutDoc.clipEnd = this.duration;
- this._stream?.getAudioTracks()[0].stop();
- const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
- });
+ };
// button for starting and stopping the recording
- recordClick = (e: React.MouseEvent) => {
- if (e.button === 0 && !e.ctrlKey) {
- this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
- e.stopPropagation();
- }
- }
+ Record = (e: React.PointerEvent) => {
+ e.button === 0 &&
+ !e.ctrlKey &&
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action(() => {
+ this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
+ }),
+ false
+ );
+ };
// for play button
Play = (e?: any) => {
- let start;
- if (this._ended || this._ele!.currentTime === this.duration) {
- start = this._trimStart;
- this._ended = false;
- }
- else {
- start = this._ele!.currentTime;
+ e?.stopPropagation?.();
+
+ if (this.timeline && this._ele) {
+ const eleTime = this._ele.currentTime;
+
+ // if curr timecode outside of trim bounds, set it to start
+ let start = eleTime >= this.timeline.trimEnd || eleTime <= this.timeline.trimStart ? this.timeline.trimStart : eleTime;
+
+ // restarts clip if reached end on last play
+ if (this._finished) {
+ this._finished = false;
+ start = this.timeline.trimStart;
+ }
+
+ this.playFrom(start, this.timeline.trimEnd, true);
}
+ };
- this.playFrom(start, this._trimEnd, true);
- e?.stopPropagation?.();
- }
+ // pause play back
+ @action
+ Pause = () => {
+ if (this._ele) {
+ this._ele.pause();
+ this.mediaState = media_state.Paused;
+
+ // if paused in the middle of playback, prevents restart on next play
+ if (!this._finished) clearTimeout(this._play);
+ this.removeCurrentlyPlaying();
+ }
+ };
- // creates a text document for dictation
+ // for dictation button, creates a text document for dictation
onFile = (e: any) => {
- const newDoc = CurrentUserUtils.GetNewTextDoc(
- "",
- NumCast(this.props.Document.x),
- NumCast(this.props.Document.y) +
- NumCast(this.props.Document._height) +
- 10,
- NumCast(this.props.Document._width),
- 2 * NumCast(this.props.Document._height)
- );
- Doc.GetProto(newDoc).recordingSource = this.dataDoc;
- Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(
- `self.recordingSource["${this.props.fieldKey}-recordingStart"]`
- );
- Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(
- "self.recordingSource.mediaState"
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action(() => {
+ const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
+ Doc.GetProto(newDoc).recordingSource = this.dataDoc;
+ Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`);
+ Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState');
+ if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) {
+ newDoc.x = this.rootDoc.x;
+ newDoc.y = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
+ Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc);
+ } else {
+ this.props.addDocument?.(newDoc);
+ }
+ }),
+ false
);
- this.props.addDocument?.(newDoc);
- e.stopPropagation();
- }
+ };
- // ref for updating time
+ // sets <audio> ref for updating time
setRef = (e: HTMLAudioElement | null) => {
- e?.addEventListener("timeupdate", this.timecodeChanged);
- e?.addEventListener("ended", this.Pause);
+ e?.addEventListener('timeupdate', this.timecodeChanged);
+ e?.addEventListener('ended', () => {
+ this._finished = true;
+ this.Pause();
+ });
this._ele = e;
- }
-
- // returns the path of the audio file
- @computed get path() {
- const field = Cast(this.props.Document[this.props.fieldKey], AudioField);
- const path = field instanceof AudioField ? field.url.href : "";
- return path === nullAudio ? "" : path;
- }
-
- // returns the html audio element
- @computed get audio() {
- return <audio ref={this.setRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
- <source src={this.path} type="audio/mpeg" />
- Not supported.
- </audio>;
- }
+ };
// pause the time during recording phase
- @action
- recordPause = (e: React.MouseEvent) => {
- this._pauseStart = new Date().getTime();
- this._paused = true;
- this._recorder.pause();
- e.stopPropagation();
- }
+ recordPause = (e: React.PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action(() => {
+ this._pauseStart = new Date().getTime();
+ this._paused = true;
+ this._recorder.pause();
+ }),
+ false
+ );
+ };
// continue the recording
- @action
- recordPlay = (e: React.MouseEvent) => {
- this._pauseEnd = new Date().getTime();
- this._paused = false;
- this._recorder.resume();
- e.stopPropagation();
- }
+ recordPlay = (e: React.PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action(() => {
+ this._paused = false;
+ this._pausedTime += new Date().getTime() - this._pauseStart;
+ this._recorder.resume();
+ }),
+ false
+ );
+ };
- playing = () => this.mediaState === "playing";
+ // plays link
playLink = (link: Doc) => {
- const stack = this._stackedTimeline.current;
if (link.annotationOn === this.rootDoc) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
- this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link));
+ this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
} else {
- this._ele!.currentTime = this.layoutDoc._currentTimecode =
- stack?.anchorStart(link) || 0;
+ this._ele!.currentTime = this.layoutDoc._currentTimecode = this.timeline?.anchorStart(link) || 0;
}
} else {
this.links
- .filter((l) => l.anchor1 === link || l.anchor2 === link)
- .forEach((l) => {
+ .filter(l => l.anchor1 === link || l.anchor2 === link)
+ .forEach(l => {
const { la1, la2 } = this.getLinkData(l);
- const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2);
- const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2);
+ const startTime = this.timeline?.anchorStart(la1) || this.timeline?.anchorStart(la2);
+ const endTime = this.timeline?.anchorEnd(la1) || this.timeline?.anchorEnd(la2);
if (startTime !== undefined) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
- endTime
- ? this.playFrom(startTime, endTime)
- : this.playFrom(startTime);
+ this.playFrom(startTime, endTime);
} else {
- this._ele!.currentTime = this.layoutDoc._currentTimecode =
- startTime;
+ this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime;
}
}
});
}
- }
+ };
- // shows trim controls
@action
- startTrim = () => {
- if (!this.duration) {
- this.timecodeChanged();
- }
- if (this.mediaState === "playing") {
- this.Pause();
- }
- this._trimming = true;
- }
+ timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
- // hides trim controls and displays new clip
- @action
+ timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.bottomControlsHeight);
+
+ setPlayheadTime = (time: number) => (this._ele!.currentTime = this.layoutDoc._currentTimecode = time);
+
+ playing = () => this.mediaState === media_state.Playing;
+
+ isActiveChild = () => this._isAnyChildContentActive;
+
+ // timeline dimensions
+ timelineWidth = () => this.props.PanelWidth();
+ timelineHeight = () => this.props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight);
+
+ // ends trim, hides trim controls and displays new clip
+ @undoBatch
finishTrim = () => {
- if (this.mediaState === "playing") {
- this.Pause();
- }
- this.layoutDoc.clipStart = this._trimStart;
- this.layoutDoc.clipEnd = this._trimEnd;
- this._trimming = false;
- this.setAnchorTime(Math.max(Math.min(this._trimEnd, this._ele!.currentTime), this._trimStart));
- }
+ this.Pause();
+ this.setPlayheadTime(Math.max(Math.min(this.timeline?.trimEnd || 0, this._ele!.currentTime), this.timeline?.trimStart || 0));
+ this.timeline?.StopTrimming();
+ };
+
+ // displays trim controls to start trimming clip
+ startTrim = (scope: TrimScope) => {
+ this.Pause();
+ this.timeline?.StartTrimming(scope);
+ };
+
+ // for trim button, double click displays full clip, single displays curr trim bounds
+ onClipPointerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ this.timeline &&
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action((e: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap) {
+ this.startTrim(TrimScope.All);
+ } else if (this.timeline) {
+ this.Pause();
+ this.timeline.IsTrimming !== TrimScope.None ? this.finishTrim() : this.startTrim(TrimScope.Clip);
+ }
+ })
+ );
+ };
+ // for zoom slider, sets timeline waveform zoom
+ zoom = (zoom: number) => {
+ this.timeline?.setZoom(zoom);
+ };
+
+ // for volume slider sets volume
@action
- setStartTrim = (newStart: number) => {
- this._trimStart = newStart;
- }
+ setVolume = (volume: number) => {
+ if (this._ele) {
+ this._volume = volume;
+ this._ele.volume = volume;
+ if (this._muted) {
+ this.toggleMute();
+ }
+ }
+ };
+ // toggles audio muted
@action
- setEndTrim = (newEnd: number) => {
- this._trimEnd = newEnd;
+ toggleMute = () => {
+ if (this._ele) {
+ this._muted = !this._muted;
+ this._ele.muted = this._muted;
+ }
+ };
+
+ setupTimelineDrop = (r: HTMLDivElement | null) => {
+ if (r && this.timeline) {
+ this._dropDisposer?.();
+ this._dropDisposer = DragManager.MakeDropTarget(
+ r,
+ (e, de) => {
+ const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ de.complete.docDragData && this.timeline.internalDocDrop(e, de, de.complete.docDragData, xp);
+ },
+ this.layoutDoc,
+ undefined
+ );
+ }
+ };
+
+ // UI for recording, initially displayed when new audio created in Dash
+ @computed get recordingControls() {
+ return (
+ <div className="audiobox-recorder">
+ <div className="audiobox-dictation" onPointerDown={this.onFile}>
+ <FontAwesomeIcon size="2x" icon="file-alt" />
+ </div>
+ {[media_state.Recording, media_state.Playing].includes(this.mediaState) ? (
+ <div className="recording-controls" onClick={e => e.stopPropagation()}>
+ <div className="record-button" onPointerDown={this.Record}>
+ <FontAwesomeIcon size="2x" icon="stop" />
+ </div>
+ <div className="record-button" onPointerDown={this._paused ? this.recordPlay : this.recordPause}>
+ <FontAwesomeIcon size="2x" icon={this._paused ? 'play' : 'pause'} />
+ </div>
+ <div className="record-timecode">{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}</div>
+ </div>
+ ) : (
+ <div className="audiobox-start-record" onPointerDown={this.Record}>
+ <FontAwesomeIcon icon="microphone" />
+ RECORD
+ </div>
+ )}
+ </div>
+ );
}
- isActiveChild = () => this._isAnyChildContentActive;
- timelineWhenChildContentsActiveChanged = (isActive: boolean) =>
- this.props.whenChildContentsActiveChanged(
- runInAction(() => (this._isAnyChildContentActive = isActive))
- )
- timelineScreenToLocal = () =>
- this.props
- .ScreenToLocalTransform()
- .translate(
- -AudioBox.playheadWidth,
- (-(100 - this.heightPercent) / 200) * this.props.PanelHeight()
- )
- setAnchorTime = (time: number) => {
- (this._ele!.currentTime = this.layoutDoc._currentTimecode = time);
+ // UI for playback, displayed for imported or recorded clips, hides timeline and collapses controls when node is shrunk vertically
+ @computed get playbackControls() {
+ return (
+ <div
+ className="audiobox-file"
+ style={{
+ pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? 'all' : 'none',
+ flexDirection: this.miniPlayer ? 'row' : 'column',
+ justifyContent: this.miniPlayer ? 'flex-start' : 'space-between',
+ }}>
+ <div className="audiobox-controls">
+ <div className="controls-left">
+ <div
+ className="audiobox-button"
+ title={this.mediaState === media_state.Paused ? 'play' : 'pause'}
+ onPointerDown={
+ this.mediaState === media_state.Paused
+ ? this.Play
+ : e => {
+ e.stopPropagation();
+ this.Pause();
+ }
+ }>
+ <FontAwesomeIcon icon={this.mediaState === media_state.Paused ? 'play' : 'pause'} size={'1x'} />
+ </div>
+
+ {!this.miniPlayer && (
+ <div className="audiobox-button" title={this.timeline?.IsTrimming !== TrimScope.None ? 'finish' : 'trim'} onPointerDown={this.onClipPointerDown}>
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
+ </div>
+ )}
+ </div>
+ <div className="controls-right">
+ <div
+ className="audiobox-button"
+ title={this._muted ? 'unmute' : 'mute'}
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.toggleMute();
+ }}>
+ <FontAwesomeIcon icon={this._muted ? 'volume-mute' : 'volume-up'} />
+ </div>
+ <input
+ type="range"
+ step="0.1"
+ min="0"
+ max="1"
+ value={this._muted ? 0 : this._volume}
+ className="toolbar-slider volume"
+ onPointerDown={(e: React.PointerEvent) => {
+ e.stopPropagation();
+ }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setVolume(Number(e.target.value))}
+ />
+ </div>
+ </div>
+
+ <div className="audiobox-playback" style={{ width: this.miniPlayer ? 0 : '100%' }}>
+ <div className="audiobox-timeline">{this.renderTimeline}</div>
+ </div>
+
+ {this.audio}
+
+ <div className="audiobox-timecodes">
+ <div className="timecode-current">{this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))}</div>
+ {this.miniPlayer ? (
+ <div>/</div>
+ ) : (
+ <div className="bottom-controls-middle">
+ <FontAwesomeIcon icon="search-plus" />
+ <input
+ type="range"
+ step="0.1"
+ min="1"
+ max="5"
+ value={this.timeline?._zoomFactor}
+ className="toolbar-slider"
+ id="zoom-slider"
+ onPointerDown={(e: React.PointerEvent) => {
+ e.stopPropagation();
+ }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ this.zoom(Number(e.target.value));
+ }}
+ />
+ </div>
+ )}
+
+ <div className="timecode-duration">{this.timeline && formatTime(Math.round(this.timeline.clipDuration))}</div>
+ </div>
+ </div>
+ );
}
- timelineHeight = () =>
- (((this.props.PanelHeight() * this.heightPercent) / 100) *
- this.heightPercent) /
- 100 // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline)
- timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth;
+ // gets CollectionStackedTimeline
@computed get renderTimeline() {
return (
<CollectionStackedTimeline
- ref={this._stackedTimeline}
- {...this.props}
+ ref={action((r: any) => (this._stackedTimeline = r))}
+ {...OmitKeys(this.props, ['CollectionFreeFormDocumentView']).omit}
fieldKey={this.annotationKey}
- dictationKey={this.fieldKey + "-dictation"}
+ dictationKey={this.fieldKey + '-dictation'}
mediaPath={this.path}
renderDepth={this.props.renderDepth + 1}
- startTag={"_timecodeToShow" /* audioStart */}
- endTag={"_timecodeToHide" /* audioEnd */}
- focus={DocUtils.DefaultFocus}
+ startTag={'_timecodeToShow' /* audioStart */}
+ endTag={'_timecodeToHide' /* audioEnd */}
bringToFront={emptyFunction}
CollectionView={undefined}
- duration={this.duration}
playFrom={this.playFrom}
- setTime={this.setAnchorTime}
+ setTime={this.setPlayheadTime}
playing={this.playing}
- whenChildContentsActiveChanged={
- this.timelineWhenChildContentsActiveChanged
- }
+ whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
removeDocument={this.removeDocument}
@@ -565,141 +667,28 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
playLink={this.playLink}
PanelWidth={this.timelineWidth}
PanelHeight={this.timelineHeight}
- trimming={this._trimming}
- trimStart={this._trimStart}
- trimEnd={this._trimEnd}
- trimDuration={this.trimDuration}
- setStartTrim={this.setStartTrim}
- setEndTrim={this.setEndTrim}
+ rawDuration={this.rawDuration}
/>
);
}
+ // returns the html audio element
+ @computed get audio() {
+ return (
+ <audio
+ ref={this.setRef}
+ className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}
+ onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '-duration'] = this._ele.duration))}>
+ <source src={this.path} type="audio/mpeg" />
+ Not supported.
+ </audio>
+ );
+ }
+
render() {
- const interactive =
- SnappingManager.GetIsDragging() || this.props.isContentActive()
- ? "-interactive"
- : "";
return (
- <div
- className="audiobox-container"
- onContextMenu={this.specificContextMenu}
- onClick={
- !this.path && !this._recorder ? this.recordAudioAnnotation : undefined
- }
- style={{
- pointerEvents:
- this.props.layerProvider?.(this.layoutDoc) === false
- ? "none"
- : undefined,
- }}
- >
- {!this.path ? (
- <div className="audiobox-buttons">
- <div className="audiobox-dictation" onClick={this.onFile}>
- <FontAwesomeIcon
- style={{
- width: "30px"
- }}
- icon="file-alt"
- size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
- />
- </div>
- {this.mediaState === "recording" || this.mediaState === "paused" ? (
- <div className="recording" onClick={(e) => e.stopPropagation()}>
- <div className="recording-buttons" onClick={this.recordClick}>
- <FontAwesomeIcon
- icon={"stop"}
- size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
- />
- </div>
- <div
- className="recording-buttons"
- onClick={this._paused ? this.recordPlay : this.recordPause}
- >
- <FontAwesomeIcon
- icon={this._paused ? "play" : "pause"}
- size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
- />
- </div>
- <div className="time">
- {formatTime(
- Math.round(NumCast(this.layoutDoc._currentTimecode))
- )}
- </div>
- </div>
- ) : (
- <div
- className={`audiobox-record${interactive}`}
- style={{ backgroundColor: Colors.DARK_GRAY }}
- >
- <FontAwesomeIcon icon="microphone" />
- RECORD
- </div>
- )}
- </div>
- ) : (
- <div
- className="audiobox-controls"
- style={{
- pointerEvents:
- this._isAnyChildContentActive || this.props.isContentActive()
- ? "all"
- : "none",
- }}
- >
- <div className="audiobox-dictation" />
- <div
- className="audiobox-player"
- style={{ height: `${AudioBox.heightPercent}%` }}
- >
- <div
- className="audiobox-buttons"
- title={this.mediaState === "paused" ? "play" : "pause"}
- onClick={this.mediaState === "paused" ? this.Play : this.Pause}
- >
- {" "}
- <FontAwesomeIcon
- icon={this.mediaState === "paused" ? "play" : "pause"}
- size={"1x"}
- />
- </div>
- <div
- className="audiobox-buttons"
- title={this._trimming ? "finish" : "trim"}
- onClick={this._trimming ? this.finishTrim : this.startTrim}
- >
- <FontAwesomeIcon
- icon={this._trimming ? "check" : "cut"}
- size={"1x"}
- />
- </div>
- <div
- className="audiobox-timeline"
- style={{
- top: 0,
- height: `100%`,
- left: AudioBox.playheadWidth,
- width: `calc(100% - ${AudioBox.playheadWidth}px)`,
- background: "white",
- }}
- >
- {this.renderTimeline}
- </div>
- {this.audio}
- <div className="audioBox-current-time">
- {this._trimming ?
- formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))
- : formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this._trimStart)))}
- </div>
- <div className="audioBox-total-time">
- {this._trimming || !this._trimEnd ?
- formatTime(Math.round(NumCast(this.duration)))
- : formatTime(Math.round(NumCast(this.trimDuration)))}
- </div>
- </div>
- </div>
- )}
+ <div ref={this.setupTimelineDrop} className="audiobox-container" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined }}>
+ {!this.path ? this.recordingControls : this.playbackControls}
</div>
);
}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c2a526804..284584a3d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
import { observer } from "mobx-react";
import { Doc, Opt } from "../../../fields/Doc";
import { List } from "../../../fields/List";
@@ -6,7 +6,7 @@ import { listSpec } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { DashColor, numberRange } from "../../../Utils";
+import { DashColor, numberRange, OmitKeys } from "../../../Utils";
import { DocumentManager } from "../../util/DocumentManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
@@ -21,14 +21,12 @@ import React = require("react");
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
- layerProvider: ((doc: Doc, assign?: boolean) => boolean) | undefined;
renderCutoffProvider: (doc: Doc) => boolean;
zIndex?: number;
highlight?: boolean;
jitterRotation: number;
dataTransition?: string;
replica: string;
- renderIndex: number;
CollectionFreeFormView: CollectionFreeFormView;
}
@@ -39,12 +37,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@observable _contentView: DocumentView | undefined | null;
get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
- get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.props.jitterRotation}deg)`; }
+ get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; }
get X() { return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); }
get Y() { return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); }
get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); }
get Opacity() { return this.dataProvider ? this.dataProvider.opacity : undefined; }
get Highlight() { return this.dataProvider?.highlight; }
+ @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
@computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
@computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
@@ -159,22 +158,22 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
...this.props,
CollectionFreeFormDocumentView: this.returnThis,
styleProvider: this.styleProvider,
- layerProvider: this.props.layerProvider,
ScreenToLocalTransform: this.screenToLocalTransform,
PanelWidth: this.panelWidth,
PanelHeight: this.panelHeight,
};
const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && DashColor(background).alpha() !== 1 ? "multiply" : undefined);
+ const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && (!background.startsWith("linear") && DashColor(background).alpha() !== 1) ? "multiply" : undefined);
return <div className={"collectionFreeFormDocumentView-container"}
style={{
outline: this.Highlight ? "orange solid 2px" : "",
width: this.panelWidth(),
height: this.panelHeight(),
transform: this.transform,
- transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
+ transformOrigin: '50% 50%',
+ transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
zIndex: this.ZInd,
- mixBlendMode,
+ mixBlendMode: mixBlendMode,
display: this.ZInd === -99 ? "none" : undefined
}} >
{this.props.renderCutoffProvider(this.props.Document) ?
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index d975baf9b..c229a966a 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -1,42 +1,47 @@
-import React = require("react");
-import { action } from "mobx";
-import { observer } from "mobx-react";
+import React = require('react');
+import { action } from 'mobx';
+import { observer } from 'mobx-react';
import { ColorState, SketchPicker } from 'react-color';
import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
-import { InkTool } from "../../../fields/InkField";
-import { StrCast } from "../../../fields/Types";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { SelectionManager } from "../../util/SelectionManager";
-import { undoBatch } from "../../util/UndoManager";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
-import "./ColorBox.scss";
+import { InkTool } from '../../../fields/InkField';
+import { StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { SelectionManager } from '../../util/SelectionManager';
+import { undoBatch } from '../../util/UndoManager';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
+import './ColorBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
-import { RichTextMenu } from "./formattedText/RichTextMenu";
+import { RichTextMenu } from './formattedText/RichTextMenu';
@observer
export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(ColorBox, fieldKey);
+ }
@undoBatch
@action
static switchColor(color: ColorState) {
- // Doc.UserDoc().backgroundColor = Utils.colorString(color); // bcz: this can't go here ... needs a proper home in the settings panel
SetActiveInkColor(color.hex);
SelectionManager.Views().map(view => {
- const targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory :
- view.props.Document.layout instanceof Doc ? view.props.Document.layout :
- view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
+ const targetDoc =
+ view.props.Document.dragFactory instanceof Doc
+ ? view.props.Document.dragFactory
+ : view.props.Document.layout instanceof Doc
+ ? view.props.Document.layout
+ : view.props.Document.isTemplateForField
+ ? view.props.Document
+ : Doc.GetProto(view.props.Document);
if (targetDoc) {
- if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) { // this situation typically occurs when you have a link dot
- targetDoc.backgroundColor = color.hex; // bcz: don't know how to change the color of an inline template...
- }
- else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== "") {
- Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + "-color"] = color.hex;
+ if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {
+ // this situation typically occurs when you have a link dot
+ targetDoc.backgroundColor = color.hex; // bcz: don't know how to change the color of an inline template...
+ } else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== '') {
+ Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + '-color'] = color.hex;
} else {
- Doc.Layout(view.props.Document)._backgroundColor = color.hex + (color.rgb.a ? Math.round(color.rgb.a * 255).toString(16) : ""); // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
+ Doc.Layout(view.props.Document)._backgroundColor = color.hex + (color.rgb.a ? Math.round(color.rgb.a * 255).toString(16) : ''); // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment
}
}
});
@@ -44,24 +49,34 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
render() {
const scaling = Math.min(this.layoutDoc.fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]());
- return <div className={`colorBox-container${this.isContentActive() ? "-interactive" : ""}`}
- onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} onClick={e => e.stopPropagation()}
- style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }} >
-
- <SketchPicker
- onChange={c => CurrentUserUtils.SelectedTool === InkTool.None && ColorBox.switchColor(c)}
- color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986',
- '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- />
+ return (
+ <div
+ className={`colorBox-container${this.isContentActive() ? '-interactive' : ''}`}
+ onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
+ onClick={e => e.stopPropagation()}
+ style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}>
+ <SketchPicker
+ onChange={c => Doc.ActiveTool === InkTool.None && ColorBox.switchColor(c)}
+ color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())}
+ presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ />
- <div style={{ width: this.props.PanelWidth() / scaling, display: "flex", paddingTop: "10px" }}>
- <div> {ActiveInkWidth()}</div>
- <input type="range" defaultValue={ActiveInkWidth()} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
- SetActiveInkWidth(e.target.value);
- SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(e.target.value));
- }} />
+ <div style={{ width: this.props.PanelWidth() / scaling, display: 'flex', paddingTop: '10px' }}>
+ <div> {ActiveInkWidth()}</div>
+ <input
+ type="range"
+ defaultValue={ActiveInkWidth()}
+ min={1}
+ max={100}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ SetActiveInkWidth(e.target.value);
+ SelectionManager.Views()
+ .filter(i => StrCast(i.rootDoc.type) === DocumentType.INK)
+ .map(i => (i.rootDoc.strokeWidth = Number(e.target.value)));
+ }}
+ />
+ </div>
</div>
- </div>;
+ );
}
}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 5919cd8f2..5ea6d567a 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -3,7 +3,7 @@ import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, Opt } from '../../../fields/Doc';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
@@ -76,18 +76,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
const clearButton = (which: string) => {
return <div className={`clear-button ${which}`}
onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
- onClick={e => this.clearDoc(e, `compareBox-${which}`)}>
+ onClick={e => this.clearDoc(e, which)}>
<FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
</div>;
};
const displayDoc = (which: string) => {
const whichDoc = Cast(this.dataDoc[which], Doc, null);
- //if (whichDoc?.type === DocumentType.MARKER)
+ // if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc;
return whichDoc ? <>
<DocumentView
ref={(r) => {
- whichDoc !== targetDoc && r?.focus(targetDoc);
+ whichDoc !== targetDoc && r?.focus(whichDoc);
}}
{...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
isContentActive={returnFalse}
@@ -96,7 +96,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
Document={targetDoc}
DataDoc={undefined}
hideLinkButton={true}
- pointerEvents={"none"} />
+ pointerEvents={returnNone} />
{clearButton(which)}
</> : // placeholder image if doc is missing
<div className="placeholder">
diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx
new file mode 100644
index 000000000..df4c8f937
--- /dev/null
+++ b/src/client/views/nodes/DataViz.tsx
@@ -0,0 +1,20 @@
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import './DataViz.scss';
+import { FieldView, FieldViewProps } from './FieldView';
+
+@observer
+export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(DataVizBox, fieldKey);
+ }
+
+ render() {
+ return (
+ <div>
+ <div>Hi</div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
new file mode 100644
index 000000000..592723ee9
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -0,0 +1,90 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { StrCast } from "../../../../fields/Types";
+import { ViewBoxBaseComponent } from "../../DocComponent";
+import { FieldViewProps, FieldView } from "../FieldView";
+import "./DataVizBox.scss";
+import { HistogramBox } from "./HistogramBox";
+import { TableBox } from "./TableBox";
+
+enum DataVizView {
+ TABLE = "table",
+ HISTOGRAM= "histogram"
+}
+
+
+@observer
+export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ @observable private pairs: {x: number, y:number}[] = [{x: 1, y:2}];
+
+ // TODO: nda - make this use enum values instead
+ // @observable private currView: DataVizView = DataVizView.TABLE;
+ @computed get currView() {
+ if (this.rootDoc._dataVizView) {
+ return StrCast(this.rootDoc._dataVizView);
+ } else {
+ return "table";
+ }
+ }
+
+ constructor(props: any) {
+ super(props);
+ if (!this.rootDoc._dataVizView) {
+ // TODO: nda - this might not always want to default to "table"
+ this.rootDoc._dataVizView = "table";
+ }
+ }
+
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); }
+
+ @action
+ private createPairs() {
+ const xVals: number[] = [0, 1, 2, 3, 4, 5];
+ // const yVals: number[] = [10, 20, 30, 40, 50, 60];
+ const yVals: number[] = [1, 2, 3, 4, 5, 6];
+ let pairs: {
+ x: number,
+ y:number
+ }[] = [];
+ if (xVals.length != yVals.length) return pairs;
+ for (let i = 0; i < xVals.length; i++) {
+ pairs.push({x: xVals[i], y: yVals[i]});
+ }
+ this.pairs = pairs;
+ return pairs;
+ }
+
+ @computed get selectView() {
+ switch(this.currView) {
+ case "table":
+ return (<TableBox pairs={this.pairs} />)
+ case "histogram":
+ return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)
+ }
+ }
+
+ @computed get pairVals() {
+ return this.createPairs();
+ }
+
+ componentDidMount() {
+ this.createPairs();
+ }
+
+ // handle changing the view using a button
+ @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.rootDoc._dataVizView = this.currView == "table" ? "histogram" : "table";
+ }
+
+ render() {
+ return (
+ <div className="dataViz">
+ <button onClick={(e) => this.changeViewHandler(e)}>Change View</button>
+ {this.selectView}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DrawHelper.ts b/src/client/views/nodes/DataVizBox/DrawHelper.ts
new file mode 100644
index 000000000..595cecebf
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DrawHelper.ts
@@ -0,0 +1,247 @@
+export class PIXIPoint {
+ public get x() { return this.coords[0]; }
+ public get y() { return this.coords[1]; }
+ public set x(value: number) { this.coords[0] = value; }
+ public set y(value: number) { this.coords[1] = value; }
+ public coords: number[] = [0, 0];
+ constructor(x: number, y: number) {
+ this.coords[0] = x;
+ this.coords[1] = y;
+ }
+}
+
+export class PIXIRectangle {
+ public x: number;
+ public y: number;
+ public width: number;
+ public height: number;
+ public get left() { return this.x; }
+ public get right() { return this.x + this.width; }
+ public get top() { return this.y; }
+ public get bottom() { return this.top + this.height; }
+ public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); }
+ constructor(x: number, y: number, width: number, height: number) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+}
+
+export class MathUtil {
+
+ public static EPSILON: number = 0.001;
+
+ public static Sign(value: number): number {
+ return value >= 0 ? 1 : -1;
+ }
+
+ public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
+ if (inline) {
+ p1.x += p2.x;
+ p1.y += p2.y;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x + p2.x, p1.y + p2.y);
+ }
+ }
+
+ public static Perp(p1: PIXIPoint): PIXIPoint {
+ return new PIXIPoint(-p1.y, p1.x);
+ }
+
+ public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint {
+ if (inline) {
+ p1.x /= by;
+ p1.y /= by;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x / by, p1.y / by);
+ }
+ }
+
+ public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) {
+ if (inline) {
+ p1.x *= by;
+ p1.y *= by;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x * by, p1.y * by);
+ }
+ }
+
+ public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
+ if (inline) {
+ p1.x -= p2.x;
+ p1.y -= p2.y;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x - p2.x, p1.y - p2.y);
+ }
+ }
+
+ public static Area(rect: PIXIRectangle): number {
+ return rect.width * rect.height;
+ }
+
+ public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) {
+ // Return minimum distance between line segment vw and point p
+ var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt
+ if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case
+ // Consider the line extending the segment, parameterized as v + t (w - v).
+ // We find projection of point p onto the line.
+ // It falls where t = [(p-v) . (w-v)] / |w-v|^2
+ // We clamp t from [0,1] to handle points outside the segment vw.
+ var dot = MathUtil.Dot(
+ MathUtil.SubtractPoint(p, v),
+ MathUtil.SubtractPoint(w, v)) / l2;
+ var t = Math.max(0, Math.min(1, dot));
+ // Projection falls on the segment
+ var projection = MathUtil.AddPoint(v,
+ MathUtil.MultiplyConstant(
+ MathUtil.SubtractPoint(w, v), t));
+ return MathUtil.Dist(p, projection);
+ }
+
+ public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined {
+ var a1 = pe1.y - ps1.y;
+ var b1 = ps1.x - pe1.x;
+
+ var a2 = pe2.y - ps2.y;
+ var b2 = ps2.x - pe2.x;
+
+ var delta = a1 * b2 - a2 * b1;
+ if (delta === 0) {
+ return undefined;
+ }
+ var c2 = a2 * ps2.x + b2 * ps2.y;
+ var c1 = a1 * ps1.x + b1 * ps1.y;
+ var invdelta = 1 / delta;
+ return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta);
+ }
+
+ public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
+ if (p.x < rect.left - this.EPSILON) {
+ return false;
+ }
+ if (p.x > rect.right + this.EPSILON) {
+ return false;
+ }
+ if (p.y < rect.top - this.EPSILON) {
+ return false;
+ }
+ if (p.y > rect.bottom + this.EPSILON) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> {
+ var r1 = new PIXIPoint(rect.left, rect.top);
+ var r2 = new PIXIPoint(rect.right, rect.top);
+ var r3 = new PIXIPoint(rect.right, rect.bottom);
+ var r4 = new PIXIPoint(rect.left, rect.bottom);
+ var ret = new Array<PIXIPoint>();
+ var dist = this.Dist(lineFrom, lineTo);
+ var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ return ret;
+ }
+
+ public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle {
+ const left = Math.max(rect1.x, rect2.x);
+ const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width);
+ const top = Math.max(rect1.y, rect2.y);
+ const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height);
+ return new PIXIRectangle(left, top, right - left, bottom - top);
+ }
+
+ public static Dist(p1: PIXIPoint, p2: PIXIPoint): number {
+ return Math.sqrt(MathUtil.DistSquared(p1, p2));
+ }
+
+ public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
+ return p1.x * p2.x + p1.y * p2.y;
+ }
+
+ public static Normalize(p1: PIXIPoint) {
+ var d = this.Length(p1);
+ return new PIXIPoint(p1.x / d, p1.y / d);
+ }
+
+ public static Length(p1: PIXIPoint): number {
+ return Math.sqrt(p1.x * p1.x + p1.y * p1.y);
+ }
+
+ public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number {
+ const a = p1.x - p2.x;
+ const b = p1.y - p2.y;
+ return (a * a + b * b);
+ }
+
+ public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
+ return !(r2.x > r1.x + r1.width ||
+ r2.x + r2.width < r1.x ||
+ r2.y > r1.y + r1.height ||
+ r2.y + r2.height < r1.y);
+ }
+
+ public static ArgMin(temp: number[]): number {
+ let index = 0;
+ let value = temp[0];
+ for (let i = 1; i < temp.length; i++) {
+ if (temp[i] < value) {
+ value = temp[i];
+ index = i;
+ }
+ }
+ return index;
+ }
+
+ public static ArgMax(temp: number[]): number {
+ let index = 0;
+ let value = temp[0];
+ for (let i = 1; i < temp.length; i++) {
+ if (temp[i] > value) {
+ value = temp[i];
+ index = i;
+ }
+ }
+ return index;
+ }
+
+ public static Combinations<T>(chars: T[]) {
+ let result = new Array<T>();
+ let f = (prefix: any, chars: any) => {
+ for (let i = 0; i < chars.length; i++) {
+ result.push(prefix.concat(chars[i]));
+ f(prefix.concat(chars[i]), chars.slice(i + 1));
+ }
+ };
+ f([], chars);
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.scss b/src/client/views/nodes/DataVizBox/HistogramBox.scss
new file mode 100644
index 000000000..5aac9dc77
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/HistogramBox.scss
@@ -0,0 +1,18 @@
+// change the stroke color of line-svg class
+.svgLine {
+ position: absolute;
+ background: darkGray;
+ stroke: #000;
+ stroke-width: 1px;
+ width:100%;
+ height:100%;
+ opacity: 0.4;
+}
+
+.svgContainer {
+ position: absolute;
+ top:0;
+ left:0;
+ width:100%;
+ height: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx
new file mode 100644
index 000000000..00dc2ef46
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/HistogramBox.tsx
@@ -0,0 +1,159 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { Doc } from "../../../../fields/Doc";
+import { NumCast } from "../../../../fields/Types";
+import "./HistogramBox.scss";
+
+interface HistogramBoxProps {
+ rootDoc: Doc;
+ pairs: {
+ x: number,
+ y: number
+ }[]
+}
+
+
+export class HistogramBox extends React.Component<HistogramBoxProps> {
+
+ private origin = {x: 0.1 * this.width, y: 0.9 * this.height};
+
+ @computed get width() {
+ return NumCast(this.props.rootDoc.width);
+ }
+
+ @computed get height() {
+ return NumCast(this.props.rootDoc.height);
+ }
+
+ @computed get x() {
+ return NumCast(this.props.rootDoc.x);
+ }
+
+ @computed get y() {
+ return NumCast(this.props.rootDoc.y);
+ }
+
+ @computed get generatePoints() {
+ // evenly distribute points along the x axis
+ const xVals: number[] = this.props.pairs.map(p => p.x);
+ const yVals: number[] = this.props.pairs.map(p => p.y);
+
+ const xMin = Math.min(...xVals);
+ const xMax = Math.max(...xVals);
+ const yMin = Math.min(...yVals);
+ const yMax = Math.max(...yVals);
+
+ const xRange = xMax - xMin;
+ const yRange = yMax - yMin;
+
+ const xScale = this.width / xRange;
+ const yScale = this.height / yRange;
+
+ const xOffset = (this.x + (0.1 * this.width)) - xMin * xScale;
+ const yOffset = (this.y + (0.25 * this.height)) - yMin * yScale;
+
+ const points: {
+ x: number,
+ y: number
+ }[] = this.props.pairs.map(p => {
+ return {
+ x: (p.x * xScale + xOffset) + this.origin.x,
+ y: (p.y * yScale + yOffset)
+ }
+ });
+
+ return points;
+ }
+
+ @computed get generateGraphLine() {
+ const points = this.generatePoints;
+ // loop through points and create a line from each point to the next
+ let lines: {
+ x1: number,
+ y1: number,
+ x2: number,
+ y2: number
+ }[] = [];
+ for (let i = 0; i < points.length - 1; i++) {
+ lines.push({
+ x1: points[i].x,
+ y1: points[i].y,
+ x2: points[i + 1].x,
+ y2: points[i + 1].y
+ });
+ }
+ // generate array of svg with lines
+ let svgLines: JSX.Element[] = [];
+ for (let i = 0; i < lines.length; i++) {
+ svgLines.push(
+ <line
+ className="svgLine"
+ key={i}
+ x1={lines[i].x1}
+ y1={lines[i].y1}
+ x2={lines[i].x2}
+ y2={lines[i].y2}
+ stroke="black"
+ strokeWidth={2}
+ />
+ );
+ }
+
+ let res = [];
+ for (let i = 0; i < svgLines.length; i++) {
+ res.push(<svg className="svgContainer">{svgLines[i]}</svg>)
+ }
+ return res;
+ }
+
+ @computed get generateAxes() {
+
+ const xAxis = {
+ x1: 0.1 * this.width,
+ x2: 0.9 * this.width,
+ y1: 0.9 * this.height,
+ y2: 0.9 * this.height,
+ };
+
+ const yAxis = {
+ x1: 0.1 * this.width,
+ x2: 0.1 * this.width,
+ y1: 0.25 * this.height,
+ y2: 0.9 * this.height,
+ };
+
+
+ return (
+ [
+ (<svg className="svgContainer">
+ {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={this.width - (0.1 * this.width)} y2={xAxis} /> */}
+ <line className="svgLine" x1={xAxis.x1} y1={xAxis.y1} x2={xAxis.x2} y2={xAxis.y2}/>
+
+ {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */}
+ </svg>),
+ (
+ <svg className="svgContainer">
+ <line className="svgLine" x1={yAxis.x1} y1={yAxis.y1} x2={yAxis.x2} y2={yAxis.y2} />
+ {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */}
+ </svg>)
+ ]
+ )
+ }
+
+
+ render() {
+ return (
+ <div>histogram box
+ {/* <svg className="svgContainer">
+ {this.generateSVGLine}
+ </svg> */}
+ {this.generateAxes[0]}
+ {this.generateAxes[1]}
+ {this.generateGraphLine.map(line => line)}
+ </div>
+ )
+
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/TableBox.scss b/src/client/views/nodes/DataVizBox/TableBox.scss
new file mode 100644
index 000000000..1264d6a46
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/TableBox.scss
@@ -0,0 +1,22 @@
+.table {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.table-row {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 5px;
+ border-bottom: 1px solid #ccc;
+}
+
+.table-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/TableBox.tsx b/src/client/views/nodes/DataVizBox/TableBox.tsx
new file mode 100644
index 000000000..dfa8262d8
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/TableBox.tsx
@@ -0,0 +1,37 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+
+interface TableBoxProps {
+ pairs: {x: number, y:number}[]
+}
+
+
+export class TableBox extends React.Component<TableBoxProps> {
+
+
+
+ render() {
+ return (
+ <div className="table-container">
+ <table className="table">
+ <thead>
+ <tr className="table-row">
+ <th>x</th>
+ <th>y</th>
+ </tr>
+ </thead>
+ <tbody>
+ {this.props.pairs.map(p => {
+ return (<tr className="table-row">
+ <td>{p.x}</td>
+ <td>{p.y}</td>
+ </tr>)
+ })}
+ </tbody>
+ </table>
+ </div>
+ )
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 005133eb0..381436a56 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,46 +1,48 @@
-import { computed } from "mobx";
-import { observer } from "mobx-react";
-import { AclPrivate, Doc, Opt } from "../../../fields/Doc";
-import { ScriptField } from "../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../fields/Types";
-import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
-import { emptyPath, OmitKeys, Without } from "../../../Utils";
-import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/collectionSchema/CollectionSchemaView";
-import { CollectionView } from "../collections/CollectionView";
-import { InkingStroke } from "../InkingStroke";
-import { PresElementBox } from "../nodes/trails/PresElementBox";
-import { SearchBox } from "../search/SearchBox";
-import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
-import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
-import { AudioBox } from "./AudioBox";
-import { ColorBox } from "./ColorBox";
-import { ComparisonBox } from "./ComparisonBox";
-import { DocumentViewProps } from "./DocumentView";
-import "./DocumentView.scss";
-import { EquationBox } from "./EquationBox";
-import { FieldView, FieldViewProps } from "./FieldView";
-import { FilterBox } from "./FilterBox";
-import { FontIconBox } from "./button/FontIconBox";
-import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox";
-import { FunctionPlotBox } from "./FunctionPlotBox";
-import { ImageBox } from "./ImageBox";
-import { KeyValueBox } from "./KeyValueBox";
-import { LabelBox } from "./LabelBox";
-import { LinkAnchorBox } from "./LinkAnchorBox";
-import { LinkBox } from "./LinkBox";
-import { PDFBox } from "./PDFBox";
-import { PresBox } from "./trails/PresBox";
-import { ScreenshotBox } from "./ScreenshotBox";
-import { ScriptingBox } from "./ScriptingBox";
-import { SliderBox } from "./SliderBox";
-import { VideoBox } from "./VideoBox";
-import { WebBox } from "./WebBox";
-import React = require("react");
-import XRegExp = require("xregexp");
-import { MapBox } from "./MapBox/MapBox";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { AclPrivate, Doc, Opt } from '../../../fields/Doc';
+import { ScriptField } from '../../../fields/ScriptField';
+import { Cast, StrCast } from '../../../fields/Types';
+import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
+import { emptyPath, OmitKeys, Without } from '../../../Utils';
+import { DirectoryImportBox } from '../../util/Import & Export/DirectoryImportBox';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
+import { CollectionView } from '../collections/CollectionView';
+import { InkingStroke } from '../InkingStroke';
+import { PresElementBox } from '../nodes/trails/PresElementBox';
+import { SearchBox } from '../search/SearchBox';
+import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
+import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
+import { AudioBox } from './AudioBox';
+import { FontIconBox } from './button/FontIconBox';
+import { ColorBox } from './ColorBox';
+import { ComparisonBox } from './ComparisonBox';
+import { DataVizBox } from './DataVizBox/DataVizBox';
+import { DocumentViewProps } from './DocumentView';
+import './DocumentView.scss';
+import { EquationBox } from './EquationBox';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FilterBox } from './FilterBox';
+import { FormattedTextBox, FormattedTextBoxProps } from './formattedText/FormattedTextBox';
+import { FunctionPlotBox } from './FunctionPlotBox';
+import { ImageBox } from './ImageBox';
+import { KeyValueBox } from './KeyValueBox';
+import { LabelBox } from './LabelBox';
+import { LinkAnchorBox } from './LinkAnchorBox';
+import { LinkBox } from './LinkBox';
+import { MapBox } from './MapBox/MapBox';
+import { PDFBox } from './PDFBox';
+import { RecordingBox } from './RecordingBox';
+import { ScreenshotBox } from './ScreenshotBox';
+import { ScriptingBox } from './ScriptingBox';
+import { SliderBox } from './SliderBox';
+import { PresBox } from './trails/PresBox';
+import { VideoBox } from './VideoBox';
+import { WebBox } from './WebBox';
+import React = require('react');
+import XRegExp = require('xregexp');
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -58,7 +60,6 @@ class ObserverJsxParser1 extends JsxParser {
const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
-
interface HTMLtagProps {
Document: Doc;
RootDoc: Doc;
@@ -66,16 +67,17 @@ interface HTMLtagProps {
onClick?: ScriptField;
onInput?: ScriptField;
scaling: number;
+ children?: JSX.Element[];
}
//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
-//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
-// transform='rotate({2*this.x+this.y}deg)'
+//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
+// transform='rotate({2*this.x+this.y}deg)'
// onClick = { this.bannerColor = this.bannerColor === 'red' ? 'green' : 'red' } >
// <ImageBox {...props} fieldKey={'data'}/>
-// <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px'
-// transform='translate(-40px, 45px) rotate(-45deg)' position='absolute'
-// color='{this.bannerColor===`green`?`light`:`dark`}blue'
+// <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px'
+// transform='translate(-40px, 45px) rotate(-45deg)' position='absolute'
+// color='{this.bannerColor===`green`?`light`:`dark`}blue'
// backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'>
// {this.title}
// </HTMLspan>
@@ -85,45 +87,51 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
click = (e: React.MouseEvent) => {
const clickScript = (this.props as any).onClick as Opt<ScriptField>;
clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, scale: this.props.scaling });
- }
+ };
onInput = (e: React.FormEvent<HTMLDivElement>) => {
const onInputScript = (this.props as any).onInput as Opt<ScriptField>;
onInputScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, value: (e.target as any).textContent });
- }
+ };
render() {
const style: { [key: string]: any } = {};
- const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "scaling", "Document", "key", "onInput", "onClick", "__proto__"]).omit;
- const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string || "";
+ const divKeys = OmitKeys(this.props, ['children', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
+ const replacer = (match: any, expr: string, offset: any, string: any) => {
+ // bcz: this executes a script to convert a propery expression string: { script } into a value
+ return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
};
Object.keys(divKeys).map((prop: string) => {
const p = (this.props as any)[prop] as string;
style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
});
const Tag = this.props.htmltag as keyof JSX.IntrinsicElements;
- return <Tag style={style} onClick={this.click} onInput={this.onInput as any}>
- {this.props.children}
- </Tag>;
+ return (
+ <Tag style={style} onClick={this.click} onInput={this.onInput as any}>
+ {this.props.children}
+ </Tag>
+ );
}
}
@observer
-export class DocumentContentsView extends React.Component<DocumentViewProps & FormattedTextBoxProps & {
- isSelected: (outsideReaction: boolean) => boolean,
- select: (ctrl: boolean) => void,
- scaling?: () => number,
- setHeight: (height: number) => void,
- layoutKey: string,
-}> {
+export class DocumentContentsView extends React.Component<
+ DocumentViewProps &
+ FormattedTextBoxProps & {
+ isSelected: (outsideReaction: boolean) => boolean;
+ select: (ctrl: boolean) => void;
+ NativeDimScaling?: () => number;
+ setHeight?: (height: number) => void;
+ layoutKey: string;
+ }
+> {
@computed get layout(): string {
TraceMobx();
if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
- if (!this.layoutDoc) return "<p>awaiting layout</p>";
- if (this.props.layoutKey === "layout_keyValue") return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
- const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string");
- if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : "");
- if (typeof layout === "string") return layout;
- return "<p>Loading layout</p>";
+ if (!this.layoutDoc) return '<p>awaiting layout</p>';
+ if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString('data'));
+ const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, 'layout')], 'string');
+ if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(this.layoutDoc.proto ? 'proto' : '');
+ if (typeof layout === 'string') return layout;
+ return '<p>Loading layout</p>';
}
get dataDoc() {
@@ -134,37 +142,38 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
const params = StrCast(this.props.Document.PARAMS);
// bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script
// const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
- const template: Doc = this.props.LayoutTemplate?.() || (this.props.LayoutTemplateString && this.props.Document) ||
+ const template: Doc =
+ this.props.LayoutTemplate?.() ||
+ (this.props.LayoutTemplateString && this.props.Document) ||
(this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) ||
Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
- return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
+ return Doc.expandTemplateLayout(template, this.props.Document, params ? '(' + params + ')' : this.props.layoutKey);
}
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
- const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
- "freezeDimensions",
- "hideResizeHandles",
- "hideTitle",
- "treeViewDoc",
- "contentPointerEvents",
- "radialMenu",
- "LayoutTemplateString",
- "LayoutTemplate",
- "dontCenter",
- "ContentScaling",
- "contextMenuItems",
- "onClick",
- "onDoubleClick",
- "onPointerDown",
- "onPointerUp",
+ const docOnlyProps = [
+ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
+ 'hideResizeHandles',
+ 'hideTitle',
+ 'treeViewDoc',
+ 'contentPointerEvents',
+ 'radialMenu',
+ 'LayoutTemplateString',
+ 'LayoutTemplate',
+ 'dontCenter',
+ 'contextMenuItems',
+ 'onClick',
+ 'onDoubleClick',
+ 'onPointerDown',
+ 'onPointerUp',
];
const list = {
- ...OmitKeys(this.props, [...docOnlyProps], "").omit,
+ ...OmitKeys(this.props, [...docOnlyProps], '').omit,
RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc,
Document: this.layoutDoc,
DataDoc: this.dataDoc,
onClick: onClick,
- onInput: onInput
+ onInput: onInput,
};
return { props: list };
}
@@ -179,17 +188,17 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
// replace code content with a script >{content}< as in <HTMLdiv>{this.title}</HTMLdiv>
const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
- return prefix + (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "") + postfix;
+ return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string) || '') + postfix;
};
layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
- // replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
+ // replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
const replacer2 = (match: any, p1: string, offset: any, string: any) => {
- return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} scaling='${this.props.scaling?.() || 1}' htmltag='${p1}'`;
+ return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} scaling='${this.props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
};
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
- // replace /HTML<tag> with </HTMLdiv> as in: </HTMLdiv> becomes </HTMLtag>
+ // replace /HTML<tag> with </HTMLdiv> as in: </HTMLdiv> becomes </HTMLtag>
const replacer3 = (match: any, p1: string, offset: any, string: any) => {
return `</HTMLtag`;
};
@@ -199,16 +208,16 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
const makeFuncProp = (func: string) => {
const splits = layoutFrame.split(`${func}=`);
if (splits.length > 1) {
- const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] });
+ const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] });
layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1);
- const script = code[1].value.replace(/^‘/, "").replace(/’$/, ""); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
- return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: "number", value: "string" });
+ const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
+ return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: 'number', value: 'string' });
}
return undefined;
// add input function to props
};
- const onClick = makeFuncProp("onClick");
- const onInput = makeFuncProp("onInput");
+ const onClick = makeFuncProp('onClick');
+ const onInput = makeFuncProp('onInput');
const bindings = this.CreateBindings(onClick, onInput);
return { bindings, layoutFrame };
}
@@ -217,24 +226,55 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
TraceMobx();
const { bindings, layoutFrame } = this.renderData;
- return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
+ return this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
<ObserverJsxParser
key={42}
blacklistedAttrs={emptyPath}
renderInWrapper={false}
components={{
- FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView,
- CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, FunctionPlotBox,
- ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox, MapBox,
+ FormattedTextBox,
+ ImageBox,
+ DirectoryImportBox,
+ FontIconBox,
+ LabelBox,
+ EquationBox,
+ SliderBox,
+ FieldView,
+ CollectionFreeFormView,
+ CollectionDockingView,
+ CollectionSchemaView,
+ CollectionView,
+ WebBox,
+ KeyValueBox,
+ PDFBox,
+ VideoBox,
+ AudioBox,
+ RecordingBox,
+ PresBox,
+ YoutubeBox,
+ PresElementBox,
+ SearchBox,
+ FilterBox,
+ FunctionPlotBox,
+ ColorBox,
+ DashWebRTCVideo,
+ LinkAnchorBox,
+ InkingStroke,
+ LinkBox,
+ ScriptingBox,
+ MapBox,
ScreenshotBox,
- HTMLtag, ComparisonBox
+ DataVizBox,
+ HTMLtag,
+ ComparisonBox,
}}
bindings={bindings}
jsx={layoutFrame}
showWarnings={true}
-
- onError={(test: any) => { console.log("DocumentContentsView:" + test); }}
- />;
+ onError={(test: any) => {
+ console.log('DocumentContentsView:' + test);
+ }}
+ />
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 433a0bf48..56de2d1fc 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -1,3 +1,4 @@
+
import { observer } from "mobx-react";
import * as React from "react";
import { DocumentView } from "./DocumentView";
@@ -51,7 +52,7 @@ export class DocumentIconContainer extends React.Component {
};
},
getVars() {
- const docs = DocumentManager.Instance.DocumentViews;
+ const docs = Array.from(DocumentManager.Instance.DocumentViews);
const capturedVariables: { [name: string]: Field } = {};
usedDocuments.forEach(index => capturedVariables[`d${index}`] = docs[index].props.Document);
return { capturedVariables };
@@ -59,6 +60,6 @@ export class DocumentIconContainer extends React.Component {
};
}
render() {
- return DocumentManager.Instance.DocumentViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+ return Array.from(DocumentManager.Instance.DocumentViews).map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 9ab3171d3..0f3eb14bc 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -2,7 +2,9 @@
.documentLinksButton-wrapper {
transform-origin: top left;
+ width: 100%;
}
+
.documentLinksButton-menu {
width: 100%;
height: 100%;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 7e6ca4248..5939d1680 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,28 +1,24 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, DocListCastAsync, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { Cast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
-import { Hypothesis } from "../../util/HypothesisUtils";
-import { LinkManager } from "../../util/LinkManager";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { Colors } from "../global/globalEnums";
-import { LightboxView } from "../LightboxView";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Opt } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { Hypothesis } from '../../util/HypothesisUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { Colors } from '../global/globalEnums';
import './DocumentLinksButton.scss';
-import { DocumentView } from "./DocumentView";
-import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
-import { TaskCompletionBox } from "./TaskCompletedBox";
-import React = require("react");
-import { Transform } from "../../util/Transform";
+import { DocumentView } from './DocumentView';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
+import { TaskCompletionBox } from './TaskCompletedBox';
+import React = require('react');
-const higflyout = require("@hig/flyout");
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -32,7 +28,7 @@ interface DocumentLinksButtonProps {
AlwaysOn?: boolean;
InMenu?: boolean;
StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
- ContentScaling?: () => number;
+ scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -46,80 +42,71 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@observable public static invisibleWebDoc: Opt<Doc>;
public static invisibleWebRef = React.createRef<HTMLDivElement>();
- @action public static ClearLinkEditor() { DocumentLinksButton.LinkEditorDocView = undefined; }
-
- @action @undoBatch
+ @action
+ @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
if (this.props.InMenu && this.props.StartLink) {
if (this._linkButton.current !== null) {
- const linkDrag = UndoManager.StartBatch("Drag Link");
- this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
- dragComplete: dropEv => {
- if (this.props.View && dropEv.linkDocument) {// dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
- !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = "hyperlink");
- }
- linkDrag?.end();
- },
- hideSource: false
- });
+ const linkDrag = UndoManager.StartBatch('Drag Link');
+ this.props.View &&
+ DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
+ dragComplete: dropEv => {
+ if (this.props.View && dropEv.linkDocument) {
+ // dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+ !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = 'hyperlink');
+ }
+ linkDrag?.end();
+ },
+ hideSource: false,
+ });
return true;
}
return false;
}
return false;
- }
+ };
onLinkMenuOpen = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
- if (doubleTap) {
- const rootDoc = this.props.View.rootDoc;
- const docid = Doc.CurrentUserEmail + Doc.GetProto(rootDoc)[Id] + "-pivotish";
- DocServer.GetRefField(docid).then(async docx => {
- const rootAlias = () => {
- const rootAlias = Doc.MakeAlias(rootDoc);
- rootAlias.x = rootAlias.y = 0;
- return rootAlias;
- };
- let wid = rootDoc[WidthSym]();
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([rootAlias()], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid);
- const docs = await DocListCastAsync(Doc.GetProto(target).data);
- if (!target.pivotFocusish) (Doc.GetProto(target).pivotFocusish = target);
- DocListCast(rootDoc.links).forEach(link => {
- const other = LinkManager.getOppositeAnchor(link, rootDoc);
- const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
- if (otherdoc && !docs?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
- const alias = Doc.MakeAlias(otherdoc);
- alias.x = wid;
- alias.y = 0;
- alias._lockedPosition = false;
- wid += otherdoc[WidthSym]();
- Doc.AddDocToList(Doc.GetProto(target), "data", alias);
- }
- });
- LightboxView.SetLightboxDoc(target);
- });
- }
- else DocumentLinksButton.LinkEditorDocView = this.props.View;
- }));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ this.onLinkButtonMoved,
+ emptyFunction,
+ action((e, doubleTap) => {
+ if (doubleTap) {
+ DocumentView.showBackLinks(this.props.View.rootDoc);
+ }
+ }),
+ undefined,
+ undefined,
+ action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View))
+ );
+ };
@undoBatch
onLinkButtonDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
- if (doubleTap && this.props.InMenu && this.props.StartLink) {
- //action(() => Doc.BrushDoc(this.props.View.Document));
- if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
- DocumentLinksButton.StartLink = undefined;
- DocumentLinksButton.StartLinkView = undefined;
- } else {
- DocumentLinksButton.StartLink = this.props.View.props.Document;
- DocumentLinksButton.StartLinkView = this.props.View;
+ setupMoveUpEvents(
+ this,
+ e,
+ this.onLinkButtonMoved,
+ emptyFunction,
+ action((e, doubleTap) => {
+ if (doubleTap && this.props.InMenu && this.props.StartLink) {
+ //action(() => Doc.BrushDoc(this.props.View.Document));
+ if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ } else {
+ DocumentLinksButton.StartLink = this.props.View.props.Document;
+ DocumentLinksButton.StartLinkView = this.props.View;
+ }
}
- }
- }));
- }
+ })
+ );
+ };
- @action @undoBatch
+ @action
+ @undoBatch
onLinkClick = (e: React.MouseEvent): void => {
if (this.props.InMenu && this.props.StartLink) {
DocumentLinksButton.AnnotationId = undefined;
@@ -127,109 +114,125 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
- } else { //if this LinkButton's Document is undefined
+ } else {
+ //if this LinkButton's Document is undefined
DocumentLinksButton.StartLink = this.props.View.props.Document;
DocumentLinksButton.StartLinkView = this.props.View;
}
//action(() => Doc.BrushDoc(this.props.View.Document));
- } else if (!this.props.InMenu) {
- DocumentLinksButton.LinkEditorDocView = this.props.View;
}
- }
-
+ };
completeLink = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action((e, doubleTap) => {
- if (doubleTap && !this.props.StartLink) {
- if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
- DocumentLinksButton.StartLink = undefined;
- DocumentLinksButton.StartLinkView = undefined;
- DocumentLinksButton.AnnotationId = undefined;
- } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
- const sourceDoc = DocumentLinksButton.StartLink;
- const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
- const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "links"); //why is long drag here when this is used for completing links by clicking?
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoBatch(
+ action((e, doubleTap) => {
+ if (doubleTap && !this.props.StartLink) {
+ if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ DocumentLinksButton.AnnotationId = undefined;
+ } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
+ const sourceDoc = DocumentLinksButton.StartLink;
+ const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
+ const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, 'links'); //why is long drag here when this is used for completing links by clicking?
- LinkManager.currentLink = linkDoc;
+ LinkManager.currentLink = linkDoc;
- runInAction(() => {
- if (linkDoc) {
- TaskCompletionBox.textDisplayed = "Link Created";
- TaskCompletionBox.popupX = e.screenX;
- TaskCompletionBox.popupY = e.screenY - 133;
- TaskCompletionBox.taskCompleted = true;
+ runInAction(() => {
+ if (linkDoc) {
+ TaskCompletionBox.textDisplayed = 'Link Created';
+ TaskCompletionBox.popupX = e.screenX;
+ TaskCompletionBox.popupY = e.screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
- LinkDescriptionPopup.popupX = e.screenX;
- LinkDescriptionPopup.popupY = e.screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
- const rect = document.body.getBoundingClientRect();
- if (LinkDescriptionPopup.popupX + 200 > rect.width) {
- LinkDescriptionPopup.popupX -= 190;
- TaskCompletionBox.popupX -= 40;
- }
- if (LinkDescriptionPopup.popupY + 100 > rect.height) {
- LinkDescriptionPopup.popupY -= 40;
- TaskCompletionBox.popupY -= 40;
- }
+ const rect = document.body.getBoundingClientRect();
+ if (LinkDescriptionPopup.popupX + 200 > rect.width) {
+ LinkDescriptionPopup.popupX -= 190;
+ TaskCompletionBox.popupX -= 40;
+ }
+ if (LinkDescriptionPopup.popupY + 100 > rect.height) {
+ LinkDescriptionPopup.popupY -= 40;
+ TaskCompletionBox.popupY -= 40;
+ }
- setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2500
+ );
+ }
+ });
}
- });
- }
- }
- })));
- }
+ }
+ })
+ )
+ );
+ };
- public static finishLinkClick = undoBatch(action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView,) => {
- if (startLink === endLink) {
- DocumentLinksButton.StartLink = undefined;
- DocumentLinksButton.StartLinkView = undefined;
- DocumentLinksButton.AnnotationId = undefined;
- DocumentLinksButton.AnnotationUri = undefined;
- //!this.props.StartLink
- } else if (startLink !== endLink) {
- endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
- startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
- const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "link", undefined, undefined, true);
+ public static finishLinkClick = undoBatch(
+ action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView) => {
+ if (startLink === endLink) {
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ DocumentLinksButton.AnnotationId = undefined;
+ DocumentLinksButton.AnnotationUri = undefined;
+ //!this.props.StartLink
+ } else if (startLink !== endLink) {
+ endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
+ startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
+ const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined, undefined, undefined, true);
- LinkManager.currentLink = linkDoc;
+ LinkManager.currentLink = linkDoc;
- if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation
- Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
- Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
- Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
- const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
- Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId,
- (startIsAnnotation ? startLink : endLink)); // edit annotation to add a Dash hyperlink to the linked doc
- }
+ if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
+ // if linking from a Hypothes.is annotation
+ Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
+ Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
+ Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
+ const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
+ Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
+ }
- if (linkDoc) {
- TaskCompletionBox.textDisplayed = "Link Created";
- TaskCompletionBox.popupX = screenX;
- TaskCompletionBox.popupY = screenY - 133;
- TaskCompletionBox.taskCompleted = true;
+ if (linkDoc) {
+ TaskCompletionBox.textDisplayed = 'Link Created';
+ TaskCompletionBox.popupX = screenX;
+ TaskCompletionBox.popupY = screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
- if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
- LinkDescriptionPopup.popupX = screenX;
- LinkDescriptionPopup.popupY = screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
- }
+ if (LinkDescriptionPopup.showDescriptions === 'ON' || !LinkDescriptionPopup.showDescriptions) {
+ LinkDescriptionPopup.popupX = screenX;
+ LinkDescriptionPopup.popupY = screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
+ }
- const rect = document.body.getBoundingClientRect();
- if (LinkDescriptionPopup.popupX + 200 > rect.width) {
- LinkDescriptionPopup.popupX -= 190;
- TaskCompletionBox.popupX -= 40;
- }
- if (LinkDescriptionPopup.popupY + 100 > rect.height) {
- LinkDescriptionPopup.popupY -= 40;
- TaskCompletionBox.popupY -= 40;
- }
+ const rect = document.body.getBoundingClientRect();
+ if (LinkDescriptionPopup.popupX + 200 > rect.width) {
+ LinkDescriptionPopup.popupX -= 190;
+ TaskCompletionBox.popupX -= 40;
+ }
+ if (LinkDescriptionPopup.popupY + 100 > rect.height) {
+ LinkDescriptionPopup.popupY -= 40;
+ TaskCompletionBox.popupY -= 40;
+ }
- setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
+ setTimeout(
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
+ 2500
+ );
+ }
}
- }
- }));
+ })
+ );
@action clearLinks() {
DocumentLinksButton.StartLink = undefined;
@@ -240,9 +243,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const results = [] as Doc[];
const filters = this.props.View.props.docFilters();
Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => {
- if (DocUtils.FilterDocs([link], filters, []).length ||
- DocUtils.FilterDocs([link.anchor2 as Doc], filters, []).length ||
- DocUtils.FilterDocs([link.anchor1 as Doc], filters, []).length) {
+ if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.anchor2 as Doc], filters, []).length || DocUtils.FilterDocs([link.anchor1 as Doc], filters, []).length) {
results.push(link);
}
});
@@ -251,47 +252,45 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
/**
* gets the JSX of the link button (btn used to start/complete links) OR the link-view button (btn on bottom left of each linked node)
- *
+ *
* todo:glr / anh seperate functionality such as onClick onPointerDown of link menu button
*/
@computed get linkButtonInner() {
- const btnDim = "30px";
- const link = <img style={{ width: "22px", height: "16px" }} src={`/assets/${"link.png"}`} />;
- const isActive = (DocumentLinksButton.StartLink === this.props.View.props.Document) && this.props.StartLink;
- return (!this.props.InMenu ?
- <div className="documentLinksButton-cont"
- style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}
- >
- <div className={"documentLinksButton"}
- onPointerDown={this.onLinkMenuOpen} onClick={this.onLinkClick}
+ const btnDim = '30px';
+ const link = <img style={{ width: '22px', height: '16px' }} src={`/assets/${'link.png'}`} />;
+ const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink;
+ return !this.props.InMenu ? (
+ <div className="documentLinksButton-cont" style={{ left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] }}>
+ <div
+ className={'documentLinksButton'}
+ onPointerDown={this.onLinkMenuOpen}
+ onClick={this.onLinkClick}
style={{
backgroundColor: Colors.LIGHT_BLUE,
color: Colors.BLACK,
- fontSize: "20px",
+ fontSize: '20px',
width: btnDim,
height: btnDim,
}}>
{Array.from(this.filteredLinks).length}
</div>
</div>
- :
- <div className="documentLinksButton-menu" ref={this._linkButton}>
- {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node
- <div className={"documentLinksButton-endLink"}
+ ) : (
+ <div className="documentLinksButton-menu">
+ {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node
+ <div
+ className={'documentLinksButton-endLink'}
+ ref={this._linkButton}
onPointerDown={DocumentLinksButton.StartLink && this.completeLink}
onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}>
<FontAwesomeIcon className="documentdecorations-icon" icon="link" />
</div>
- : (null)
- }
- {
- this.props.InMenu && this.props.StartLink ? //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
- <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
- </div>
- :
- (null)
- }
+ ) : null}
+ {this.props.InMenu && this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+ <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? undefined : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
+ </div>
+ ) : null}
</div>
);
}
@@ -299,21 +298,23 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
render() {
TraceMobx();
- const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
- const buttonTitle = "Tap to view links; double tap to open link collection";
+ const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
+ const buttonTitle = 'Tap to view links; double tap to open link collection';
const title = this.props.InMenu ? menuTitle : buttonTitle;
//render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
- return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) ? (null) :
- <div className="documentLinksButton-wrapper" >
- {
- (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) ||
- (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ?
- <Tooltip title={<div className="dash-tooltip">{title}</div>}>
- {this.linkButtonInner}
- </Tooltip>
- : this.linkButtonInner
- }
- </div>;
+ return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? null : (
+ <div
+ className="documentLinksButton-wrapper"
+ style={{
+ transform: this.props.InMenu ? undefined : `scale(${this.props.scaling})`,
+ }}>
+ {(this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? (
+ <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>
+ ) : (
+ this.linkButtonInner
+ )}
+ </div>
+ );
}
}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 4565f8504..c02692bfb 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,11 +1,16 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
.documentView-effectsWrapper {
border-radius: inherit;
}
+// documentViews have a docView-hack tag which is replaced by this tag when capturing bitmaps (when the dom is converted to an html string)
+.documentView-hack {
+ display: inline; // this swap is needed because for some reason when capturing bitmaps, things don't appear unless dispay:inline is explicitly set.
+}
+
.documentView-customBorder {
- width:100%;
+ width: 100%;
height: 100%;
position: absolute;
top: 0;
@@ -19,7 +24,7 @@
width: 100%;
height: 100%;
border-radius: inherit;
- transition: outline .3s linear;
+ transition: outline 0.3s linear;
cursor: grab;
// background: $white; //overflow: hidden;
@@ -57,7 +62,7 @@
.documentView-audioBackground {
display: inline-block;
- width: 10%;
+ width: 25px;
height: 25;
position: absolute;
top: 10px;
@@ -83,7 +88,7 @@
width: 100%;
overflow: hidden;
- >.documentView-node {
+ > .documentView-node {
position: absolute;
}
}
@@ -153,7 +158,7 @@
top: 0;
width: 100%;
height: 14;
- background: rgba(0, 0, 0, .4);
+ background: rgba(0, 0, 0, 0.4);
text-align: center;
text-overflow: ellipsis;
white-space: pre;
@@ -182,19 +187,18 @@
transition: opacity 0.5s;
}
}
-
}
.documentView-node:hover,
.documentView-node-topmost:hover {
- >.documentView-styleWrapper {
- >.documentView-titleWrapper-hover {
+ > .documentView-styleWrapper {
+ > .documentView-titleWrapper-hover {
display: inline-block;
}
}
- >.documentView-styleWrapper {
- >.documentView-captionWrapper {
+ > .documentView-styleWrapper {
+ > .documentView-captionWrapper {
opacity: 1;
}
}
@@ -220,6 +224,6 @@
.documentView-node:first-child {
position: relative;
- background: "#B59B66"; //$white;
+ background: '#B59B66'; //$white;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8682a43e6..1ee1aec5a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,54 +1,57 @@
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
-import { List } from "../../../fields/List";
-import { ObjectField } from "../../../fields/ObjectField";
-import { listSpec } from "../../../fields/Schema";
+import { List } from '../../../fields/List';
+import { ObjectField } from '../../../fields/ObjectField';
+import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { AudioField } from "../../../fields/URLField";
+import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnTrue, returnVal, simulateMouseClick, Utils } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DocumentType } from '../../documents/DocumentTypes';
-import { Networking } from "../../Network";
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager, dropActionType } from "../../util/DragManager";
+import { DocServer } from '../../DocServer';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { Networking } from '../../Network';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { InteractionUtils } from '../../util/InteractionUtils';
+import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from "../../util/SelectionManager";
+import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
-import { Transform } from "../../util/Transform";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionView, CollectionViewType } from '../collections/CollectionView';
-import { ContextMenu } from "../ContextMenu";
+import { Transform } from '../../util/Transform';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { CollectionView } from '../collections/CollectionView';
+import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from "../DocComponent";
+import { DocComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
-import { InkingStroke } from "../InkingStroke";
-import { LightboxView } from "../LightboxView";
-import { StyleLayers, StyleProp } from "../StyleProvider";
-import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "./DocumentContentsView";
+import { InkingStroke } from '../InkingStroke';
+import { LightboxView } from '../LightboxView';
+import { StyleProp } from '../StyleProvider';
+import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { DocumentContentsView } from './DocumentContentsView';
import { DocumentLinksButton } from './DocumentLinksButton';
-import "./DocumentView.scss";
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
+import './DocumentView.scss';
+import { FieldViewProps } from './FieldView';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { LinkAnchorBox } from './LinkAnchorBox';
-import { LinkDocPreview } from "./LinkDocPreview";
+import { LinkDocPreview } from './LinkDocPreview';
import { RadialMenu } from './RadialMenu';
-import { ScriptingBox } from "./ScriptingBox";
+import { ScriptingBox } from './ScriptingBox';
import { PresBox } from './trails/PresBox';
-import React = require("react");
+import React = require('react');
const { Howl } = require('howler');
interface Window {
@@ -62,16 +65,16 @@ declare class MediaRecorder {
export enum ViewAdjustment {
resetView = 1,
- doNothing = 0
+ doNothing = 0,
}
-export const ViewSpecPrefix = "viewSpec"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
+export const ViewSpecPrefix = 'viewSpec'; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
export interface DocFocusOptions {
originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
- willZoom?: boolean; // determines whether to zoom in on target document
- scale?: number; // percent of containing frame to zoom into document
- afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document
+ willZoom?: boolean; // determines whether to zoom in on target document
+ scale?: number; // percent of containing frame to zoom into document
+ afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document
docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
}
@@ -79,11 +82,12 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>
export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
export interface DocComponentView {
+ updateIcon?: () => void; // updates the icon representation of the document
getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
- setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
- reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
- shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
+ setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+ reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
+ shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
isAnyChildContentActive?: () => boolean; // is any child content of the document active
getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
@@ -96,26 +100,34 @@ export interface DocComponentView {
annotationKey?: string;
getTitle?: () => string;
getScrollHeight?: () => number;
- getCenter?: (xf: Transform) => { X: number, Y: number };
- ptToScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
- ptFromScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
- snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number };
+ getCenter?: (xf: Transform) => { X: number; Y: number };
+ ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
+ ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
+ snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
}
+// These props are passed to both FieldViews and DocumentViews
export interface DocumentViewSharedProps {
+ fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
+ DocumentView?: () => DocumentView;
renderDepth: number;
Document: Doc;
DataDoc?: Doc;
- fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
+ contentBounds?: () => undefined | { x: number; y: number; r: number; b: number };
+ fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitContentsToBox property on a Document
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
+ suppressSetHeight?: boolean;
thumbShown?: () => boolean;
+ isHovering?: () => boolean;
setContentView?: (view: DocComponentView) => any;
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
PanelHeight: () => number;
docViewPath: () => DocumentView[];
- layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
+ childHideDecorationTitle?: () => boolean;
+ childHideResizeHandles?: () => boolean;
+ dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
styleProvider: Opt<StyleProviderFunc>;
focus: DocFocusFunc;
fitWidth?: (doc: Doc) => boolean;
@@ -126,7 +138,7 @@ export interface DocumentViewSharedProps {
whenChildContentsActiveChanged: (isActive: boolean) => void;
rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
addDocTab: (doc: Doc, where: string) => boolean;
- filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+ filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
addDocument?: (doc: Doc | Doc[]) => boolean;
removeDocument?: (doc: Doc | Doc[]) => boolean;
moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
@@ -140,38 +152,44 @@ export interface DocumentViewSharedProps {
ignoreAutoHeight?: boolean;
forceAutoHeight?: boolean;
disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
createNewFilterDoc?: () => void;
updateFilterDoc?: (doc: Doc) => void;
- // Parker added both of these
- originalBackgroundColor?: string;
- isNoteTakingView?: boolean;
+ dontHideOnDrag?: boolean;
}
+
+// these props are specific to DocuentViews
export interface DocumentViewProps extends DocumentViewSharedProps {
// properties specific to DocumentViews but not to FieldView
- freezeDimensions?: boolean;
hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
- hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDocumentButtonBar?: boolean;
+ hideOpenButton?: boolean;
+ hideDeleteButton?: boolean;
treeViewDoc?: Doc;
isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
radialMenu?: String[];
LayoutTemplateString?: string;
- dontCenter?: "x" | "y" | "xy";
- ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
+ dontCenter?: 'x' | 'y' | 'xy';
+ dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
NativeWidth?: () => number;
NativeHeight?: () => number;
+ NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
LayoutTemplate?: () => Opt<Doc>;
- contextMenuItems?: () => { script: ScriptField, filter?: ScriptField, label: string, icon: string }[];
+ contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
onClick?: () => ScriptField;
onDoubleClick?: () => ScriptField;
onPointerDown?: () => ScriptField;
onPointerUp?: () => ScriptField;
+ onBrowseClick?: () => ScriptField | undefined;
+ onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
}
+// these props are only available in DocumentViewIntenral
export interface DocumentViewInternalProps extends DocumentViewProps {
NativeWidth: () => number;
NativeHeight: () => number;
@@ -184,6 +202,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
@observer
export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
+ _animateScaleTime = 300; // milliseconds;
@observable _animateScalingTo = 0;
@observable _mediaState = 0;
@observable _pendingDoubleClick = false;
@@ -202,34 +221,88 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
@observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
- private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; }
- public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
- public get ContentDiv() { return this._mainCont.current; }
- public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); }
- @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
- @computed get ContentScale() { return this.props.ContentScaling?.() || 1; }
- @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); }
- @computed get hidden() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Hidden); }
- @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); }
- @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); }
- @computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); }
- @computed get hideLinkButton() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.props.isSelected() ? ":selected" : "")); }
- @computed get widgetDecorations() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ":selected" : "")); }
- @computed get backgroundColor() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); }
- @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
- @computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
- @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; }
- @computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
- @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
- @computed get nativeWidth() { return this.props.NativeWidth(); }
- @computed get nativeHeight() { return this.props.NativeHeight(); }
- @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
- @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
- @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
- @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
-
- componentWillUnmount() { this.cleanupHandlers(true); }
- componentDidMount() { this.setupHandlers(); }
+ private get topMost() {
+ return this.props.renderDepth === 0 && !LightboxView.LightboxDoc;
+ }
+ public get displayName() {
+ return 'DocumentView(' + this.props.Document.title + ')';
+ } // this makes mobx trace() statements more descriptive
+ public get ContentDiv() {
+ return this._mainCont.current;
+ }
+ public get LayoutFieldKey() {
+ return Doc.LayoutFieldKey(this.layoutDoc);
+ }
+ @computed get ShowTitle() {
+ return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
+ }
+ @computed get NativeDimScaling() {
+ return this.props.NativeDimScaling?.() || 1;
+ }
+ @computed get thumb() {
+ return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
+ }
+ @computed get hidden() {
+ return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden);
+ }
+ @computed get opacity() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity);
+ }
+ @computed get boxShadow() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow);
+ }
+ @computed get borderRounding() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+ }
+ @computed get hideLinkButton() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.props.isSelected() ? ':selected' : ''));
+ }
+ @computed get widgetDecorations() {
+ return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ':selected' : ''));
+ }
+ @computed get backgroundColor() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+ }
+ @computed get docContents() {
+ return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents);
+ }
+ @computed get headerMargin() {
+ return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ }
+ @computed get titleHeight() {
+ return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
+ }
+ @computed get pointerEvents() {
+ return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ':selected' : ''));
+ }
+ @computed get finalLayoutKey() {
+ return StrCast(this.Document.layoutKey, 'layout');
+ }
+ @computed get nativeWidth() {
+ return this.props.NativeWidth();
+ }
+ @computed get nativeHeight() {
+ return this.props.NativeHeight();
+ }
+ @computed get onClickHandler() {
+ return this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+ }
+ @computed get onDoubleClickHandler() {
+ return this.props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
+ }
+ @computed get onPointerDownHandler() {
+ return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
+ }
+ @computed get onPointerUpHandler() {
+ return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
+ }
+
+ componentWillUnmount() {
+ this.cleanupHandlers(true);
+ }
+ componentDidMount() {
+ this.setupHandlers();
+ }
//componentDidUpdate() { this.setupHandlers(); }
setupHandlers() {
this.cleanupHandlers(false);
@@ -239,6 +312,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
}
}
+ @action
cleanupHandlers(unbrush: boolean) {
this._dropDisposer?.();
this._multiTouchDisposer?.();
@@ -250,8 +324,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.removeEndListeners();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
if (RadialMenu.Instance._display === false) {
this.addHoldMoveListeners();
this.addHoldEndListeners();
@@ -260,7 +334,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this._firstX = pt.pageX;
this._firstY = pt.pageY;
}
- }
+ };
handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
@@ -271,7 +345,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
this.handle1PointerHoldEnd(e, me);
}
- }
+ };
handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
this.removeHoldMoveListeners();
@@ -286,10 +360,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (RadialMenu.Instance.used) {
this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY);
}
- }
+ };
handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble && !this.props.isSelected()) {
+ if (!this.props.isSelected()) {
e.stopPropagation();
e.preventDefault();
@@ -298,7 +372,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.removeEndListeners();
this.addEndListeners();
}
- }
+ };
handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
SelectionManager.DeselectAll();
@@ -307,33 +381,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (touch) {
this._downX = touch.clientX;
this._downY = touch.clientY;
- if (!e.nativeEvent.cancelBubble) {
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation();
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
+ if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
e.stopPropagation();
}
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ e.stopPropagation();
}
- }
+ };
handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
if (e.cancelBubble && this.props.isDocumentActive?.()) {
this.removeMoveListeners();
- }
- else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+ } else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
const touch = me.touchEvent.changedTouches.item(0);
if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
this.cleanUpInteractions();
- this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
+ this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined);
}
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
- }
+ };
@action
handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
@@ -344,8 +417,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const oldPoint2 = this.prevPoints.get(pt2.identifier);
const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
if (pinching !== 0 && oldPoint1 && oldPoint2) {
- const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
- const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
+ const dW = Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX);
+ const dH = Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY);
const dX = -1 * Math.sign(dW);
const dY = -1 * Math.sign(dH);
@@ -354,33 +427,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const layoutDoc = Document(Doc.Layout(this.props.Document));
let nwidth = Doc.NativeWidth(layoutDoc);
let nheight = Doc.NativeHeight(layoutDoc);
- const width = (layoutDoc._width || 0);
- const height = (layoutDoc._height || (nheight / nwidth * width));
- const scale = this.props.ScreenToLocalTransform().Scale * this.ContentScale;
- const actualdW = Math.max(width + (dW * scale), 20);
- const actualdH = Math.max(height + (dH * scale), 20);
+ const width = layoutDoc._width || 0;
+ const height = layoutDoc._height || (nheight / nwidth) * width;
+ const scale = this.props.ScreenToLocalTransform().Scale * this.NativeDimScaling;
+ const actualdW = Math.max(width + dW * scale, 20);
+ const actualdH = Math.max(height + dH * scale, 20);
doc.x = (doc.x || 0) + dX * (actualdW - width);
doc.y = (doc.y || 0) + dY * (actualdH - height);
const fixedAspect = e.ctrlKey || (nwidth && nheight);
if (fixedAspect && (!nwidth || !nheight)) {
- Doc.SetNativeWidth(layoutDoc, nwidth = layoutDoc._width || 0);
- Doc.SetNativeHeight(layoutDoc, nheight = layoutDoc._height || 0);
+ Doc.SetNativeWidth(layoutDoc, (nwidth = layoutDoc._width || 0));
+ Doc.SetNativeHeight(layoutDoc, (nheight = layoutDoc._height || 0));
}
if (nwidth > 0 && nheight > 0) {
if (Math.abs(dW) > Math.abs(dH)) {
if (!fixedAspect) {
- Doc.SetNativeWidth(layoutDoc, actualdW / (layoutDoc._width || 1) * Doc.NativeWidth(layoutDoc));
+ Doc.SetNativeWidth(layoutDoc, (actualdW / (layoutDoc._width || 1)) * Doc.NativeWidth(layoutDoc));
}
layoutDoc._width = actualdW;
- if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+ if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = (nheight / nwidth) * layoutDoc._width;
else layoutDoc._height = actualdH;
- }
- else {
+ } else {
if (!fixedAspect) {
- Doc.SetNativeHeight(layoutDoc, actualdH / (layoutDoc._height || 1) * Doc.NativeHeight(doc));
+ Doc.SetNativeHeight(layoutDoc, (actualdH / (layoutDoc._height || 1)) * Doc.NativeHeight(doc));
}
layoutDoc._height = actualdH;
- if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+ if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = (nwidth / nheight) * layoutDoc._height;
else layoutDoc._width = actualdW;
}
} else {
@@ -392,7 +464,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
e.stopPropagation();
e.preventDefault();
}
- }
+ };
@action
onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
@@ -401,132 +473,162 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
- (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
+ (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) &&
+ 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, "add:right"), 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 });
+ 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();
- }
+ };
startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
if (this._mainCont.current) {
- const dragData = new DragManager.DocumentDragData([this.props.Document]); //put a set of
- if (this.props.isNoteTakingView) {
- dragData.draggedDocuments.forEach((doc) => {
- doc.backgroundColor = "#C9DAEF"
- doc.opacity = 0.5
- })
- }
- const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
- dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
+ const dragData = new DragManager.DocumentDragData([this.props.Document]);
+ const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
+ dragData.offset = this.props
+ .ScreenToLocalTransform()
+ .scale(this.NativeDimScaling)
+ .transformDirection(x - left, y - top);
+ dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]);
+ dragData.offset[1] = Math.min(this.rootDoc[HeightSym](), dragData.offset[1]);
dragData.dropAction = dropAction;
dragData.treeViewDoc = this.props.treeViewDoc;
dragData.removeDocument = this.props.removeDocument;
dragData.moveDocument = this.props.moveDocument;
- //dragData.dimSource :
- // dragEffects field, set dim
+ //dragData.dimSource :
+ // dragEffects field, set dim
// add kv pairs to a doc, swap properties with the node while dragging, and then swap when dropping
// add a dragEffects prop to DocumentView as a function that sets up. Each view has its own prop, when you start dragging:
- // in Draganager, figure out which doc(s) you're dragging and change what opacity function returns
+ // in Draganager, figure out which doc(s) you're dragging and change what opacity function returns
const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
- DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStar &&!this.props.isNoteTakingView)},
- () => setTimeout(action(() => {
- ffview && (ffview.ChildDrag = undefined)
- //TODO: is there a better way than adding another field to the props? Not quite sure how "this" works tbh
- if (this.props.isNoteTakingView) {
- this.props.Document.backgroundColor = "";
- this.props.Document.opacity = 1;
- }
- }))); // this needs to happen after the drop event is processed.
+ ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }, () =>
+ setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))
+ ); // this needs to happen after the drop event is processed.
ffview?.setupDragLines(false);
}
}
onKeyDown = (e: React.KeyboardEvent) => {
- if (e.altKey && !e.nativeEvent.cancelBubble) {
+ if (e.altKey) {
e.stopPropagation();
e.preventDefault();
- if (e.key === "†" || e.key === "t") {
- if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = "title";
+ if (e.key === '†' || e.key === 't') {
+ if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = 'title';
if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
- else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
+ else if (!this._titleRef.current.setIsFocused(true)) {
+ // if focus didn't change, focus on interior text...
this._titleRef.current?.setIsFocused(false);
this._componentView?.setFocus?.();
}
}
}
- }
+ };
focus = (anchor: Doc, options?: DocFocusOptions) => {
- LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
+ LightboxView.SetCookie(StrCast(anchor['cookies-set']));
// copying over VIEW fields immediately allows the view type to switch to create the right _componentView
- Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
- this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
- });
+ Array.from(Object.keys(Doc.GetProto(anchor)))
+ .filter(key => key.startsWith(ViewSpecPrefix))
+ .forEach(spec => {
+ this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec]);
+ });
// after a timeout, the right _componentView should have been created, so call it to update its view spec values
setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
- const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
- const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
+ const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+ const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing;
this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
- ...options, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
+ ...options,
+ afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus || focusSpeed !== undefined) : ViewAdjustment.doNothing), focusSpeed ?? 0)),
});
-
- }
+ };
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
- if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
- (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
let stopPropagate = true;
let preventDefault = true;
- !StrListCast(this.props.Document._layerTags).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
- if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name);
+ (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
+ if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {
+ // && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
if (this._timeout) {
clearTimeout(this._timeout);
+ this._pendingDoubleClick = false;
this._timeout = undefined;
}
- if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) {
+ // bcz: hack? don't execute script if you're clicking on a scripting box itself
const { clientX, clientY, shiftKey } = e;
- const func = () => this.onDoubleClickHandler.script.run({
- this: this.layoutDoc,
- self: this.rootDoc,
- scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView(),
- clientX, clientY, shiftKey
- }, console.log);
- UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click");
+ const func = () =>
+ this.onDoubleClickHandler.script.run(
+ {
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ scriptContext: this.props.scriptContext,
+ thisContainer: this.props.ContainingCollectionDoc,
+ documentView: this.props.DocumentView(),
+ clientX,
+ clientY,
+ shiftKey,
+ },
+ console.log
+ );
+ UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
} else if (!Doc.IsSystem(this.rootDoc)) {
- UndoManager.RunInBatch(() =>
- LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.())
- , "double tap");
+ UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, 'lightbox', this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap');
SelectionManager.DeselectAll();
Doc.UnBrushDoc(this.props.Document);
}
- } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ } else if (this.onClickHandler?.script && !isScriptBox()) {
+ // bcz: hack? don't execute script if you're clicking on a scripting box itself
const { clientX, clientY, shiftKey } = e;
- const func = () => this.onClickHandler.script.run({
- this: this.layoutDoc,
- self: this.rootDoc,
- _readOnly_: false,
- scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView(),
- clientX, clientY, shiftKey
- }, console.log).result?.select === true ? this.props.select(false) : "";
- const clickFunc = () => this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, "on click");
+ const func = () =>
+ this.onClickHandler.script.run(
+ {
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ _readOnly_: false,
+ scriptContext: this.props.scriptContext,
+ thisContainer: this.props.ContainingCollectionDoc,
+ documentView: this.props.DocumentView(),
+ clientX,
+ clientY,
+ shiftKey,
+ },
+ console.log
+ ).result?.select === true
+ ? this.props.select(false)
+ : '';
+ const clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
if (this.onDoubleClickHandler) {
- this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350);
+ runInAction(() => (this._pendingDoubleClick = true));
+ this._timeout = setTimeout(() => {
+ this._timeout = undefined;
+ clickFunc();
+ }, 350);
} else clickFunc();
- } else if (this.allLinks && this.Document.type !== DocumentType.LINK && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
- this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
+ } else if (this.allLinks && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+ this.allLinks.length && LinkFollower.FollowLink(undefined, this.props.Document, this.props, e.altKey);
} else {
- if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+ if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
+ // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
} else {
- runInAction(() => this._pendingDoubleClick = true);
- this._timeout = setTimeout(action(() => { this._pendingDoubleClick = false; this._timeout = undefined; }), 350);
+ runInAction(() => (this._pendingDoubleClick = true));
+ this._timeout = setTimeout(
+ action(() => {
+ this._pendingDoubleClick = false;
+ this._timeout = undefined;
+ }),
+ 350
+ );
this.props.select(e.ctrlKey || e.shiftKey);
}
preventDefault = false;
@@ -537,8 +639,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
});
onPointerDown = (e: React.PointerEvent): void => {
+ if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return;
// continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
- if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) {
+ if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool))) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
@@ -548,49 +651,51 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
this._downX = e.clientX;
this._downY = e.clientY;
- if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.layoutDoc.onDragStart) &&
+ if (Doc.ActiveTool === InkTool.None && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
// if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
- !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
+ if (
+ (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
+ !this.props.onBrowseClick?.() &&
!this.Document.ignoreClick &&
!e.ctrlKey &&
(e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
- !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+ !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)
+ ) {
e.stopPropagation();
// don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though
//if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
}
if (this.props.isDocumentActive?.()) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointermove', this.onPointerMove);
}
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointerup', this.onPointerUp);
+ document.addEventListener('pointerup', this.onPointerUp);
}
- }
+ };
onPointerMove = (e: PointerEvent): void => {
if (e.cancelBubble) return;
- if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) return;
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+ if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.props.dropAction || this.Document.dropAction || undefined) as dropActionType));
}
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
- }
+ };
cleanupPointerEvents = () => {
this.cleanUpInteractions();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
+ };
onPointerUp = (e: PointerEvent): void => {
this.cleanupPointerEvents();
@@ -598,62 +703,75 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
} else {
- this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+ this._doubleTap = Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2;
// bcz: this is a placeholder. documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
- if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now();// don't want to process the start of a double tap if the doucment is selected
+ if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
}
- }
+ };
- @undoBatch @action
- toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
+ @undoBatch
+ @action
+ toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => {
this.Document.ignoreClick = false;
- this.Document._isLinkButton = !this.Document._isLinkButton;
- setPushpin && (this.Document.isPushpin = this.Document._isLinkButton);
+ if (setPushpin) {
+ this.Document.isPushpin = !this.Document.isPushpin;
+ this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton;
+ } else {
+ this.Document._isLinkButton = !this.Document._isLinkButton;
+ }
if (this.Document._isLinkButton && !this.onClickHandler) {
- this.Document.followLinkZoom = zoom;
+ zoom !== undefined && (this.Document.followLinkZoom = zoom);
this.Document.followLinkLocation = location;
} else if (this.Document._isLinkButton && this.onClickHandler) {
this.Document._isLinkButton = false;
- this.Document["onClick-rawScript"] = this.dataDoc["onClick-rawScript"] = this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
+ this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
}
- }
- @undoBatch @action
+ };
+ @undoBatch
+ @action
toggleTargetOnClick = (): void => {
this.Document.ignoreClick = false;
this.Document._isLinkButton = true;
this.Document.isPushpin = true;
- }
- @undoBatch @action
- followLinkOnClick = (location: Opt<string>, zoom: boolean,): void => {
+ };
+ @undoBatch
+ @action
+ followLinkOnClick = (location: Opt<string>, zoom: boolean): void => {
this.Document.ignoreClick = false;
this.Document._isLinkButton = true;
this.Document.isPushpin = false;
this.Document.followLinkZoom = zoom;
this.Document.followLinkLocation = location;
- }
- @undoBatch @action
+ };
+ @undoBatch
+ @action
selectOnClick = (): void => {
this.Document.ignoreClick = false;
this.Document._isLinkButton = false;
this.Document.isPushpin = false;
this.Document.onClick = this.layoutDoc.onClick = undefined;
- }
+ };
@undoBatch
noOnClick = (): void => {
this.Document.ignoreClick = false;
this.Document._isLinkButton = false;
- }
+ };
@undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
- @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace("layout_", "")}")`, { documentView: "any" });
+ @undoBatch setToggleDetail = () =>
+ (this.Document.onClick = ScriptField.MakeScript(
+ `toggleDetail(documentView, "${StrCast(this.Document.layoutKey)
+ .replace('layout_', '')
+ .replace(/^layout$/, 'detail')}")`,
+ { documentView: 'any' }
+ ));
- @undoBatch @action
+ @undoBatch
+ @action
drop = async (e: Event, de: DragManager.DropEvent) => {
if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
- if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
- alert((e.target as any)?.closest?.("*.lm_content") ?
- "You can't perform this move most likely because you don't have permission to modify the destination." :
- "Linking to document tabs not yet supported. Drop link on document content.");
+ if (this.props.Document === Doc.ActiveDashboard) {
+ alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.');
return;
}
const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
@@ -665,34 +783,34 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document;
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]);
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]);
}
}
- }
+ };
@undoBatch
@action
makeIntoPortal = async () => {
const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
if (!portalLink) {
- const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
- DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
+ const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' });
+ DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, 'portal to:portal from');
}
- this.Document.followLinkLocation = "inPlace";
+ this.Document.followLinkLocation = 'inPlace';
this.Document.followLinkZoom = true;
this.Document._isLinkButton = true;
- }
+ };
@action
onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
- if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
+ if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
//!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
- if (e.button === 0 && !e.ctrlKey || e.isDefaultPrevented()) {
+ if ((e.button === 0 && !e.ctrlKey) || e.isDefaultPrevented()) {
e.preventDefault();
return;
}
@@ -700,7 +818,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
e.stopPropagation();
e.persist();
- if (!navigator.userAgent.includes("Mozilla") && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
+ if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
return;
}
}
@@ -709,17 +827,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
if (e && !(e.nativeEvent as any).dash) {
- const onDisplay = () => setTimeout(() => {
- DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ const onDisplay = () =>
setTimeout(() => {
- const ele = document.elementFromPoint(e.clientX, e.clientY);
- simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
+ DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ setTimeout(() => {
+ const ele = document.elementFromPoint(e.clientX, e.clientY);
+ simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
+ });
});
- });
- if (navigator.userAgent.includes("Macintosh")) {
+ if (navigator.userAgent.includes('Macintosh')) {
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
- }
- else {
+ } else {
onDisplay();
}
return;
@@ -727,227 +845,292 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
- cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
- this.props.contextMenuItems?.().forEach(item =>
- item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
+ cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: 'sticky-note' })
+ );
+ this.props
+ .contextMenuItems?.()
+ .forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
if (!this.props.Document.isFolder) {
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
- const appearance = cm.findByDescription("UI Controls...");
- const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
- !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
- !Doc.UserDoc().noviceMode && appearanceItems.push({
- description: "Add a Field", event: () => {
- const alias = Doc.MakeAlias(this.rootDoc);
- alias.layout = FormattedTextBox.LayoutString("newfield");
- alias.title = "newfield";
- alias._yMargin = 10;
- alias._height = 35;
- alias._width = 100;
- alias.syncLayoutFieldWithTitle = true;
- alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
- alias.y = NumCast(this.rootDoc.y);
- this.props.addDocument?.(alias);
- }, icon: "eye"
- });
- DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
- !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
+ const appearance = cm.findByDescription('UI Controls...');
+ const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
+ !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, 'add:right'), icon: 'eye' });
+ !Doc.noviceMode &&
+ appearanceItems.push({
+ description: 'Add a Field',
+ event: () => {
+ const alias = Doc.MakeAlias(this.rootDoc);
+ alias.layout = FormattedTextBox.LayoutString('newfield');
+ alias.title = 'newfield';
+ alias._height = 35;
+ alias._width = 100;
+ alias.syncLayoutFieldWithTitle = true;
+ alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
+ alias.y = NumCast(this.rootDoc.y);
+ this.props.addDocument?.(alias);
+ },
+ icon: 'eye',
+ });
+ DocListCast(this.Document.links).length &&
+ appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' });
+ !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
- if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
- !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
- const existingOnClick = cm.findByDescription("OnClick...");
- const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
+ !Doc.noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? 'Show' : 'Hide'} Audio Button`, event: action(() => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio)), icon: 'microphone' });
+ const existingOnClick = cm.findByDescription('OnClick...');
+ const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- const zorders = cm.findByDescription("ZOrder...");
- const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
+ const zorders = cm.findByDescription('ZOrder...');
+ const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
if (this.props.bringToFront !== emptyFunction) {
- zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+ zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: 'expand-arrows-alt' });
+ zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: 'expand-arrows-alt' });
+ zorderItems.push({
+ description: this.rootDoc._raiseWhenDragged !== false ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
+ event: undoBatch(action(() => (this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined))),
+ icon: 'expand-arrows-alt',
+ });
}
- !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
+ !zorders && cm.addItem({ description: 'ZOrder...', noexpand: true, subitems: zorderItems, icon: 'compass' });
- onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
- !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
- onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+ !Doc.noviceMode && onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
+ !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
+ this.props.CollectionFreeFormDocumentView &&
+ onClicks.push({
+ description: (this.Document.followLinkZoom ? "Don't" : '') + ' zoom following link',
+ event: () => (this.Document.followLinkZoom = !this.Document.followLinkZoom),
+ icon: this.Document.ignoreClick ? 'unlock' : 'lock',
+ });
if (!this.Document.annotationOn) {
- const options = cm.findByDescription("Options...");
- const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
-
- onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
- onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
- !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
- onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
- onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
- onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
- !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
+ const options = cm.findByDescription('Options...');
+ const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
+
+ onClicks.push({ description: this.Document.ignoreClick ? 'Select' : 'Do Nothing', event: () => (this.Document.ignoreClick = !this.Document.ignoreClick), icon: this.Document.ignoreClick ? 'unlock' : 'lock' });
+ onClicks.push({ description: this.Document.isLinkButton ? 'Remove Follow Behavior' : 'Follow Link in Place', event: () => this.toggleFollowLink('inPlace', true, false), icon: 'link' });
+ !this.Document.isLinkButton && onClicks.push({ description: 'Follow Link on Right', event: () => this.toggleFollowLink('add:right', false, false), icon: 'link' });
+ onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(undefined, false, false), icon: 'link' });
+ onClicks.push({ description: (this.Document.isPushpin ? 'Remove' : 'Make') + ' Pushpin', event: () => this.toggleFollowLink(undefined, false, true), icon: 'map-pin' });
+ onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (DocListCast(this.Document.links).length) {
- onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" });
- onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" });
- onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" });
- !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
+ onClicks.push({ description: 'Select on Click', event: () => this.selectOnClick(), icon: 'link' });
+ onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(undefined, false), icon: 'link' });
+ onClicks.push({ description: 'Toggle Link Target on Click', event: () => this.toggleTargetOnClick(), icon: 'map-pin' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, subitems: onClicks, icon: 'mouse-pointer' });
}
}
const funcs: ContextMenuProps[] = [];
- if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
- funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
- funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
- funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
- cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
+ if (!Doc.noviceMode && this.layoutDoc.onDragStart) {
+ funcs.push({ description: 'Drag an Alias', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
+ funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
+ funcs.push({ description: 'Drag Document', icon: 'edit', event: () => (this.layoutDoc.onDragStart = undefined) });
+ cm.addItem({ description: 'OnDrag...', noexpand: true, subitems: funcs, icon: 'asterisk' });
}
- const more = cm.findByDescription("More...");
- const moreItems = more && "subitems" in more ? more.subitems : [];
+ const more = cm.findByDescription('More...');
+ const moreItems = more && 'subitems' in more ? more.subitems : [];
if (!Doc.IsSystem(this.rootDoc)) {
- (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
- if (!Doc.UserDoc().noviceMode) {
- moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
- moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
+ (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
+ if (!Doc.noviceMode) {
+ moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: 'concierge-bell' });
+ moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
- moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
- moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
+ moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: 'caret-square-right' });
}
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" });
+ moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' });
}
}
- if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
- moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
+ if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && Doc.ActiveDashboard !== this.props.Document) {
+ // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
+ moreItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
}
-
- const help = cm.findByDescription("Help...");
- const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
- !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
- !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
- cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
+ !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
+
+ const help = cm.findByDescription('Help...');
+ const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
+ helpItems.push({ description: 'Show Fields ', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), 'add:right'), icon: 'layer-group' });
+ !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), 'add:right'), icon: 'keyboard' });
+ !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
+ !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' });
+ cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
}
if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
- }
+ };
collectionFilters = () => StrListCast(this.props.Document._docFilters);
collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
@computed get showFilterIcon() {
- return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
- this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined;
+ return this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined;
}
rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
panelHeight = () => this.props.PanelHeight() - this.headerMargin;
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
- contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
- setHeight = (height: number) => {
- if (this.props.renderDepth !== -1) {
- this.layoutDoc._height = height;
- }
- }
- setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
+ setHeight = (height: number) => (this.layoutDoc._height = height);
+ setContentView = action((view: { getAnchor?: () => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
isContentActive = (outsideReaction?: boolean) => {
- return this.props.isContentActive() === false ? false : (
- CurrentUserUtils.SelectedTool !== InkTool.None ||
- SnappingManager.GetIsDragging() ||
- this.rootSelected() ||
- this.props.Document.forceActive ||
- this.props.isSelected(outsideReaction) ||
- this._componentView?.isAnyChildContentActive?.() ||
- this.props.isContentActive()) ? true : undefined;
- }
+ return this.props.isContentActive() === false
+ ? false
+ : Doc.ActiveTool !== InkTool.None ||
+ SnappingManager.GetIsDragging() ||
+ this.rootSelected() ||
+ this.props.Document.forceActive ||
+ this.props.isSelected(outsideReaction) ||
+ this._componentView?.isAnyChildContentActive?.() ||
+ this.props.isContentActive()
+ ? true
+ : undefined;
+ };
@observable _retryThumb = 1;
- thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
- !this._componentView?.isAnyChildContentActive?.() ? true : false;
+ thumbShown = () => {
+ return !this.props.isSelected() &&
+ LightboxView.LightboxDoc !== this.rootDoc &&
+ this.thumb &&
+ !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
+ !Doc.isBrushedHighlightedDegree(this.props.Document) &&
+ !this._componentView?.isAnyChildContentActive?.()
+ ? true
+ : false;
+ };
+ linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale;
@computed get contents() {
TraceMobx();
- const audioView = !this.layoutDoc._showAudio ? (null) :
- <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter} >
- <FontAwesomeIcon className="documentView-audioFont"
- style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }}
- icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
- </div>;
- return <div className="documentView-contentsView"
- style={{
- pointerEvents: this.props.pointerEvents as any ? this.props.pointerEvents as any : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"),
- height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
- }}>
- {!this._retryThumb || !this.thumbShown() ? (null) :
- <img style={{ background: "white", top: 0, position: "absolute" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
- width={this.props.PanelWidth()} height={this.props.PanelHeight()}
- onError={(e: any) => {
- setTimeout(action(() => this._retryThumb = 0), 0);
- setTimeout(action(() => this._retryThumb = 1), 150);
- }} />}
- <DocumentContentsView key={1}
- {...this.props}
- docViewPath={this.props.viewPath}
- thumbShown={this.thumbShown}
- setContentView={this.setContentView}
- scaling={this.contentScaling}
- PanelHeight={this.panelHeight}
- setHeight={this.setHeight}
- isContentActive={this.isContentActive}
- ScreenToLocalTransform={this.screenToLocal}
- rootSelected={this.rootSelected}
- onClick={this.onClickFunc}
- focus={this.focus}
- layoutKey={this.finalLayoutKey} />
- {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
- {this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
- <DocumentLinksButton View={this.props.DocumentView()}
- ContentScaling={this.props.ContentScaling}
- Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />
- }
- {audioView}
- </div>;
+ const audioView = !this.layoutDoc._showAudio ? null : (
+ <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter}>
+ <FontAwesomeIcon
+ className="documentView-audioFont"
+ style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }}
+ icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'microphone' : 'file-audio'}
+ size="sm"
+ />
+ </div>
+ );
+
+ return (
+ <div
+ className="documentView-contentsView"
+ style={{
+ pointerEvents:
+ (this.props.pointerEvents?.() as any) ?? this.rootDoc.layoutKey === 'layout_icon'
+ ? 'none'
+ : (this.props.contentPointerEvents as any)
+ ? (this.props.contentPointerEvents as any)
+ : this.rootDoc.type !== DocumentType.INK && this.isContentActive()
+ ? 'all'
+ : 'none',
+ height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+ }}>
+ {!this._retryThumb || !this.thumbShown() ? null : (
+ <img
+ style={{ background: 'white', top: 0, position: 'relative' }}
+ src={this.thumb} // + '?d=' + (new Date()).getTime()}
+ width={this.props.PanelWidth()}
+ height={this.props.PanelHeight()}
+ onError={(e: any) => {
+ setTimeout(
+ action(() => (this._retryThumb = 0)),
+ 0
+ );
+ setTimeout(
+ action(() => (this._retryThumb = 1)),
+ 150
+ );
+ }}
+ />
+ )}
+ <DocumentContentsView
+ key={1}
+ {...this.props}
+ docViewPath={this.props.viewPath}
+ thumbShown={this.thumbShown}
+ isHovering={this.isHovering}
+ setContentView={this.setContentView}
+ NativeDimScaling={this.props.NativeDimScaling}
+ PanelHeight={this.panelHeight}
+ setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
+ isContentActive={this.isContentActive}
+ ScreenToLocalTransform={this.screenToLocal}
+ rootSelected={this.rootSelected}
+ onClick={this.onClickFunc}
+ focus={this.focus}
+ layoutKey={this.finalLayoutKey}
+ />
+ {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints}
+ {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? null : (
+ <DocumentLinksButton
+ View={this.props.DocumentView()}
+ scaling={this.linkButtonInverseScaling}
+ Offset={[this.topMost ? 0 : !this.props.isSelected() ? -15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? -15 : -30]}
+ />
+ )}
+ {audioView}
+ </div>
+ );
}
get indicatorIcon() {
- if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas";
- else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users";
- else return "user";
+ if (this.props.Document['acl-Public'] !== SharingPermissions.None) return 'globe-americas';
+ else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return 'users';
+ else return 'user';
}
@undoBatch
- hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
+ hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true);
anchorPanelWidth = () => this.props.PanelWidth() || 1;
anchorPanelHeight = () => this.props.PanelHeight() || 1;
anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
switch (property) {
- case StyleProp.ShowTitle: return "";
- case StyleProp.PointerEvents: return "none";
- case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox
- default: return this.props.styleProvider?.(doc, props, property);
+ case StyleProp.ShowTitle:
+ return '';
+ case StyleProp.PointerEvents:
+ return 'none';
+ case StyleProp.LinkSource:
+ return this.props.Document; // pass the LinkSource to the LinkAnchorBox
+ default:
+ return this.props.styleProvider?.(doc, props, property);
}
- }
- // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
- // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g.,
+ };
+ // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
+ // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g.,
// - PDF text regions are rendered as an Annotations without generating a DocumentView, '
// - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
// - and links to PDF/Web docs at a certain scroll location never create an explicit view.
// For each of these, we create LinkAnchorBox's on the border of the DocumentView.
@computed get directLinks() {
- TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(link =>
- Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
- Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
- ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
- ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
+ TraceMobx();
+ return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(
+ link =>
+ Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
+ Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
+ ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
+ ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
);
}
- @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
- @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links
+ @computed get allLinks() {
+ TraceMobx();
+ return LinkManager.Instance.getAllRelatedLinks(this.rootDoc);
+ }
+ @computed get allLinkEndpoints() {
+ // the small blue dots that mark the endpoints of links
TraceMobx();
if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
- if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
+ if (this.rootDoc.type === DocumentType.PRES || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return null;
const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
- return filtered.map((link, i) =>
+ return filtered.map((link, i) => (
<div className="documentView-anchorCont" key={link[Id]}>
- <DocumentView {...this.props}
+ <DocumentView
+ {...this.props}
+ isContentActive={returnFalse}
Document={link}
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
@@ -958,80 +1141,87 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
styleProvider={this.anchorStyleProvider}
removeDocument={this.hideLinkAnchor}
LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)} />
- </div >);
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)}
+ />
+ </div>
+ ));
}
@action
onPointerEnter = () => {
const self = this;
- const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]);
+ const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']);
if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
- anno.data instanceof AudioField && new Howl({
- src: [anno.data.url.href],
- format: ["mp3"],
- autoplay: true,
- loop: false,
- volume: 0.5,
- onend: function () {
- runInAction(() => self._mediaState = 0);
- }
- });
+ anno.data instanceof AudioField &&
+ new Howl({
+ src: [anno.data.url.href],
+ format: ['mp3'],
+ autoplay: true,
+ loop: false,
+ volume: 0.5,
+ onend: function () {
+ runInAction(() => (self._mediaState = 0));
+ },
+ });
this._mediaState = 1;
}
- }
+ };
recordAudioAnnotation = () => {
let gumStream: any;
let recorder: any;
const self = this;
- navigator.mediaDevices.getUserMedia({
- audio: true
- }).then(function (stream) {
- gumStream = stream;
- recorder = new MediaRecorder(stream);
- recorder.ondataavailable = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer(e.data);
- if (!(result instanceof Error)) {
- const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 });
- audioDoc.treeViewExpandedView = "layout";
- const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc));
- if (audioAnnos === undefined) {
- self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"] = new List([audioDoc]);
- } else {
- audioAnnos.push(audioDoc);
+ navigator.mediaDevices
+ .getUserMedia({
+ audio: true,
+ })
+ .then(function (stream) {
+ gumStream = stream;
+ recorder = new MediaRecorder(stream);
+ recorder.ondataavailable = async (e: any) => {
+ const [{ result }] = await Networking.UploadFilesToServer(e.data);
+ if (!(result instanceof Error)) {
+ const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: 'audio test', _width: 200, _height: 32 });
+ audioDoc.treeViewExpandedView = 'layout';
+ const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(Doc));
+ if (audioAnnos === undefined) {
+ self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioDoc]);
+ } else {
+ audioAnnos.push(audioDoc);
+ }
}
- }
- };
- runInAction(() => self._mediaState = 2);
- recorder.start();
- setTimeout(() => {
- recorder.stop();
- runInAction(() => self._mediaState = 0);
- gumStream.getAudioTracks()[0].stop();
- }, 5000);
- });
- }
+ };
+ runInAction(() => (self._mediaState = 2));
+ recorder.start();
+ setTimeout(() => {
+ recorder.stop();
+ runInAction(() => (self._mediaState = 0));
+ gumStream.getAudioTracks()[0].stop();
+ }, 5000);
+ });
+ };
- captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption");
+ captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
@computed get innards() {
TraceMobx();
- const ffscale = () => (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1);
- const showTitle = this.ShowTitle?.split(":")[0];
- const showTitleHover = this.ShowTitle?.includes(":hover");
+ const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
+ const showTitle = this.ShowTitle?.split(':')[0];
+ const showTitleHover = this.ShowTitle?.includes(':hover');
const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
- const captionView = !showCaption ? (null) :
- <div className="documentView-captionWrapper"
+ const captionView = !showCaption ? null : (
+ <div
+ className="documentView-captionWrapper"
style={{
- pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
minWidth: 50 * ffscale(),
- maxHeight: `max(100%, ${20 * ffscale()}px)`
+ maxHeight: `max(100%, ${20 * ffscale()}px)`,
}}>
- <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
+ <FormattedTextBox
+ {...OmitKeys(this.props, ['children']).omit}
yPadding={10}
xPadding={10}
fieldKey={showCaption}
- fontSize={12 * Math.max(1, 2 * ffscale() / 3)}
+ fontSize={12 * Math.max(1, (2 * ffscale()) / 3)}
styleProvider={this.captionStyleProvider}
dontRegisterView={true}
noSidebar={true}
@@ -1039,202 +1229,321 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
isContentActive={this.isContentActive}
onClick={this.onClickFunc}
/>
- </div>;
- const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
- const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
- const titleView = !showTitle ? (null) :
- <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
- position: this.headerMargin ? "relative" : "absolute",
- height: this.titleHeight,
- color: lightOrDark(background),
- background,
- pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
- }}>
- <EditableView ref={this._titleRef}
- contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
- display={"block"}
+ </div>
+ );
+ const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
+ const background = StrCast(
+ SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor,
+ Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : 'rgba(0,0,0,0.4)'
+ );
+ const titleView = !showTitle ? null : (
+ <div
+ className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
+ key="title"
+ style={{
+ position: this.headerMargin ? 'relative' : 'absolute',
+ height: this.titleHeight,
+ width: !this.headerMargin ? `calc(100% - 18px)` : '100%', // leave room for annotation button
+ color: lightOrDark(background),
+ background,
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+ }}>
+ <EditableView
+ ref={this._titleRef}
+ contents={showTitle
+ .split(';')
+ .map(field => field.trim())
+ .map(field => targetDoc[field]?.toString())
+ .join('\\')}
+ display={'block'}
fontSize={10}
- GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
+ GetValue={() => (showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle)}
SetValue={undoBatch((input: string) => {
- if (input?.startsWith("#")) {
+ if (input?.startsWith('#')) {
if (this.props.showTitle) {
this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined;
} else {
- Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : "creationDate";
+ Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : 'creationDate';
}
} else {
- var value = input.replace(new RegExp(showTitle + "="), "") as string | number;
- if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
- if (showTitle.includes("Date") || showTitle === "author") return true;
+ var value = input.replace(new RegExp(showTitle + '='), '') as string | number;
+ if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value);
+ if (showTitle.includes('Date') || showTitle === 'author') return true;
Doc.SetInPlace(targetDoc, showTitle, value, true);
}
return true;
})}
/>
- </div>;
- return this.props.hideTitle || (!showTitle && !showCaption) ?
- this.contents :
- <div className="documentView-styleWrapper" >
- {!this.headerMargin ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
+ </div>
+ );
+ return this.props.hideTitle || (!showTitle && !showCaption) ? (
+ this.contents
+ ) : (
+ <div className="documentView-styleWrapper">
+ {!this.headerMargin ? (
+ <>
+ {' '}
+ {this.contents} {titleView}{' '}
+ </>
+ ) : (
+ <>
+ {' '}
+ {titleView} {this.contents}{' '}
+ </>
+ )}
{captionView}
- </div>;
+ </div>
+ );
}
- @observable _: string = "";
+ isHovering = () => this._isHovering;
+ @observable _isHovering = false;
+ @observable _: string = '';
@computed get renderDoc() {
TraceMobx();
- const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png");
+ const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
const isButton = this.props.Document.type === DocumentType.FONTICON;
- if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
- return this.docContents ??
- <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
- id={this.props.Document[Id]}
- style={{
- background: isButton || thumb ? undefined : this.backgroundColor,
- opacity: this.opacity,
- color: StrCast(this.layoutDoc.color, "inherit"),
- fontFamily: StrCast(this.Document._fontFamily, "inherit"),
- fontSize: Cast(this.Document._fontSize, "string", null),
- transformOrigin: this._animateScalingTo ? "center center" : undefined,
- transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
- transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform 0.5s ease-${this._animateScalingTo < 1 ? "in" : "out"}`,
- }}>
-
- {this.innards}
- {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)}
- {this.widgetDecorations ?? null}
- </div>;
+ if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
+ return (
+ this.docContents ?? (
+ <div
+ className={`documentView-node${this.topMost ? '-topmost' : ''}`}
+ id={this.props.Document[Id]}
+ onPointerEnter={action(() => (this._isHovering = true))}
+ onPointerLeave={action(() => (this._isHovering = false))}
+ style={{
+ background: isButton || thumb ? undefined : this.backgroundColor,
+ opacity: this.opacity,
+ color: StrCast(this.layoutDoc.color, 'inherit'),
+ fontFamily: StrCast(this.Document._fontFamily, 'inherit'),
+ fontSize: Cast(this.Document._fontSize, 'string', null),
+ transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
+ transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+ }}>
+ {this.innards}
+ {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : null}
+ {this.widgetDecorations ?? null}
+ </div>
+ )
+ );
}
render() {
TraceMobx();
- const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
- const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
- const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
+ const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : Doc.DocBrushStatus.unbrushed) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
+ const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
+ const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
- highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
+ highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== '[pres element template]'; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
- const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
- this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
-
- // Return surrounding highlight
- return <div className={DocumentView.ROOT_DIV} ref={this._mainCont}
- onContextMenu={this.onContextMenu}
- onKeyDown={this.onKeyDown}
- onPointerDown={this.onPointerDown}
- onClick={this.onClick}
- onPointerEnter={e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)}
- onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)}
- style={{
- borderRadius: this.borderRounding,
- pointerEvents: this.pointerEvents,
- outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px",
- border: highlighting && this.borderRounding && highlightStyle === "dashed" ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
- boxShadow,
- clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined
- }}>
- {!borderPath.path ? internal :
- <>
- {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}>
+ const boxShadow = this.props.treeViewDoc
+ ? null
+ : highlighting && this.borderRounding && highlightStyle !== 'dashed'
+ ? `0 0 0 ${highlightIndex}px ${highlightColor}`
+ : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+
+ // Return surrounding highlight
+ return (
+ <div
+ className={`${DocumentView.ROOT_DIV} docView-hack`}
+ ref={this._mainCont}
+ onContextMenu={this.onContextMenu}
+ onKeyDown={this.onKeyDown}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
+ onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))}
+ style={{
+ display: this.hidden ? 'inline' : undefined,
+ borderRadius: this.borderRounding,
+ pointerEvents: this.pointerEvents,
+ outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : 'solid 0px',
+ border: highlighting && this.borderRounding && highlightStyle === 'dashed' ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
+ boxShadow,
+ clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined,
+ }}>
+ {!borderPath.path ? (
+ internal
+ ) : (
+ <>
+ {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}>
{internal}
</div> */}
- {internal}
- <div key="border2" className="documentView-customBorder" style={{ pointerEvents: "none" }} >
- <svg style={{ overflow: "visible" }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
- <path d={borderPath.path} style={{ stroke: "black", fill: "transparent", strokeWidth: borderPath.width }} />
- </svg>
- </div>
- </>
- }
- {this.showFilterIcon ?
- <FontAwesomeIcon icon={"filter"} size="lg"
- style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
- onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
- />
- : (null)}
- </div>;
+ {internal}
+ <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
+ <svg style={{ overflow: 'visible' }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
+ <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
+ </svg>
+ </div>
+ </>
+ )}
+ {this.showFilterIcon ? (
+ <FontAwesomeIcon
+ icon={'filter'}
+ size="lg"
+ style={{ position: 'absolute', top: '1%', right: '1%', cursor: 'pointer', padding: 1, color: this.showFilterIcon === 'hasFilter' ? '#18c718bd' : 'orange', zIndex: 1 }}
+ onPointerDown={action(e => {
+ this.props.select(false);
+ SettingsManager.propertiesWidth = 250;
+ e.stopPropagation();
+ })}
+ />
+ ) : null}
+ </div>
+ );
}
}
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
- public static ROOT_DIV = "documentView-effectsWrapper";
- public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
+ public static ROOT_DIV = 'documentView-effectsWrapper';
+ public get displayName() {
+ return 'DocumentView(' + this.props.Document?.title + ')';
+ } // this makes mobx trace() statements more descriptive
public ContentRef = React.createRef<HTMLDivElement>();
private _disposers: { [name: string]: IReactionDisposer } = {};
+ public static showBackLinks(linkSource: Doc) {
+ const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + '-pivotish';
+ DocServer.GetRefField(docid).then(docx => {
+ const rootAlias = () => {
+ const rootAlias = Doc.MakeAlias(linkSource);
+ rootAlias.x = rootAlias.y = 0;
+ return rootAlias;
+ };
+ const linkCollection =
+ (docx instanceof Doc && docx) ||
+ Docs.Create.StackingDocument(
+ [
+ /*rootAlias()*/
+ ],
+ { title: linkSource.title + '-pivot', _width: 500, _height: 500 },
+ docid
+ );
+ linkCollection.linkSource = linkSource;
+ if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript('updateLinkCollection(self)');
+ LightboxView.SetLightboxDoc(linkCollection);
+ });
+ }
+
@observable public docView: DocumentViewInternal | undefined | null;
- get Document() { return this.props.Document; }
- get topMost() { return this.props.renderDepth === 0; }
- get rootDoc() { return this.docView?.rootDoc || this.Document; }
- get dataDoc() { return this.docView?.dataDoc || this.Document; }
- get finalLayoutKey() { return this.docView?.finalLayoutKey || "layout"; }
- get ContentDiv() { return this.docView?.ContentDiv; }
- get ComponentView() { return this.docView?._componentView; }
- get allLinks() { return this.docView?.allLinks || []; }
- get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
- get fitWidth() { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; }
-
- @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
- @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
+ showContextMenu(pageX: number, pageY: number) {
+ return this.docView?.onContextMenu(undefined, pageX, pageY);
+ }
+ get Document() {
+ return this.props.Document;
+ }
+ get topMost() {
+ return this.props.renderDepth === 0;
+ }
+ get rootDoc() {
+ return this.docView?.rootDoc || this.Document;
+ }
+ get dataDoc() {
+ return this.docView?.dataDoc || this.Document;
+ }
+ get finalLayoutKey() {
+ return this.docView?.finalLayoutKey || 'layout';
+ }
+ get ContentDiv() {
+ return this.docView?.ContentDiv;
+ }
+ get ComponentView() {
+ return this.docView?._componentView;
+ }
+ get allLinks() {
+ return this.docView?.allLinks || [];
+ }
+ get LayoutFieldKey() {
+ return this.docView?.LayoutFieldKey || 'layout';
+ }
+ get fitWidth() {
+ return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth;
+ }
+
+ @computed get docViewPath(): DocumentView[] {
+ return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
+ }
+ @computed get layoutDoc() {
+ return Doc.Layout(this.Document, this.props.LayoutTemplate?.());
+ }
@computed get nativeWidth() {
- return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
- returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
+ return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, !this.fitWidth));
}
@computed get nativeHeight() {
- return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
- returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
+ return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth));
+ }
+ @computed get shouldNotScale() {
+ return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+ }
+ @computed get effectiveNativeWidth() {
+ return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
+ }
+ @computed get effectiveNativeHeight() {
+ return this.shouldNotScale ? 0 : this.nativeHeight || NumCast(this.layoutDoc.height);
}
- @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); }
- @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
- @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
@computed get nativeScaling() {
if (this.shouldNotScale) return 1;
const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
if (this.fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
- return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or fitWidth
+ return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or fitWidth
}
return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
}
- @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
+ @computed get panelWidth() {
+ return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth();
+ }
@computed get panelHeight() {
- if (this.effectiveNativeHeight) {
- return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
+ if (this.effectiveNativeHeight && !this.layoutDoc.nativeHeightUnfrozen) {
+ const scrollHeight = this.fitWidth ? Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight)) : 0;
+ return Math.min(this.props.PanelHeight(), Math.max(scrollHeight, this.effectiveNativeHeight) * this.nativeScaling);
}
return this.props.PanelHeight();
}
- @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; }
- @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; }
- @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; }
- @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; }
+ @computed get Xshift() {
+ return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0;
+ }
+ @computed get Yshift() {
+ return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && (!this.layoutDoc.nativeHeightUnfrozen || (!this.fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight()))
+ ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
+ : 0;
+ }
+ @computed get centeringX() {
+ return this.props.dontCenter?.includes('x') ? 0 : this.Xshift;
+ }
+ @computed get centeringY() {
+ return this.props.dontCenter?.includes('y') ? 0 : this.Yshift;
+ }
- toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
+ toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options);
getBounds = () => {
- if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+ if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
return undefined;
}
- const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse();
+ const xf = this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
}
return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
- }
-
- public iconify() {
- const layoutKey = Cast(this.Document.layoutKey, "string", null);
- if (layoutKey !== "layout_icon") {
- this.switchViews(true, "icon");
- if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", "");
+ };
+
+ public iconify(finished?: () => void) {
+ this.ComponentView?.updateIcon?.();
+ const layoutKey = Cast(this.Document.layoutKey, 'string', null);
+ if (layoutKey !== 'layout_icon') {
+ this.switchViews(true, 'icon', finished);
+ if (layoutKey && layoutKey !== 'layout' && layoutKey !== 'layout_icon') this.Document.deiconifyLayout = layoutKey.replace('layout_', '');
} else {
- const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null);
- this.switchViews(deiconifyLayout ? true : false, deiconifyLayout);
+ const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
+ this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
this.Document.deiconifyLayout = undefined;
+ this.props.bringToFront(this.rootDoc);
}
}
@undoBatch
@@ -1242,14 +1551,27 @@ export class DocumentView extends React.Component<DocumentViewProps> {
setCustomView = (custom: boolean, layout: string): void => {
Doc.setNativeView(this.props.Document);
custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
- }
- switchViews = action((custom: boolean, view: string) => {
- this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
- setTimeout(action(() => {
- this.setCustomView(custom, view);
- this.docView && (this.docView._animateScalingTo = 1); // expand it
- setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), 400);
- }), 400);
+ };
+ switchViews = action((custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+ this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+ setTimeout(
+ action(() => {
+ if (useExistingLayout && custom && this.rootDoc['layout_' + view]) {
+ this.rootDoc.layoutKey = 'layout_' + view;
+ } else {
+ this.setCustomView(custom, view);
+ }
+ this.docView && (this.docView._animateScalingTo = 1); // expand it
+ setTimeout(
+ action(() => {
+ this.docView && (this.docView._animateScalingTo = 0);
+ finished?.();
+ }),
+ this.docView!._animateScaleTime - 10
+ );
+ }),
+ this.docView!._animateScaleTime - 10
+ );
});
startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
@@ -1261,12 +1583,18 @@ export class DocumentView extends React.Component<DocumentViewProps> {
NativeHeight = () => this.effectiveNativeHeight;
PanelWidth = () => this.panelWidth;
PanelHeight = () => this.panelHeight;
- ContentScale = () => this.nativeScaling;
+ NativeDimScaling = () => this.nativeScaling;
selfView = () => this;
- screenToLocalTransform = () => {
- return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling);
- }
+ screenToLocalTransform = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .translate(-this.centeringX, -this.centeringY)
+ .scale(1 / this.nativeScaling);
componentDidMount() {
+ this._disposers.reactionScript = reaction(
+ () => ScriptCast(this.rootDoc.reactionScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result,
+ output => output
+ );
this._disposers.height = reaction(
() => NumCast(this.layoutDoc._height),
action(height => {
@@ -1285,37 +1613,76 @@ export class DocumentView extends React.Component<DocumentViewProps> {
TraceMobx();
const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+ const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
- return (<div className="contentFittingDocumentView">
- {!this.props.Document || !this.props.PanelWidth() ? (null) : (
- <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
- style={{
- position: this.props.Document.isInkMask ? "absolute" : undefined,
- transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
- `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
- }}>
- <DocumentViewInternal {...this.props}
- DocumentView={this.selfView}
- viewPath={this.docViewPathFunc}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- NativeWidth={this.NativeWidth}
- NativeHeight={this.NativeHeight}
- isSelected={this.isSelected}
- select={this.select}
- ContentScaling={this.ContentScale}
- ScreenToLocalTransform={this.screenToLocalTransform}
- focus={this.props.focus || emptyFunction}
- bringToFront={emptyFunction}
- ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
- </div>)}
- </div>);
+ return (
+ <div className="contentFittingDocumentView">
+ {!this.props.Document || !this.props.PanelWidth() ? null : (
+ <div
+ className="contentFittingDocumentView-previewDoc"
+ ref={this.ContentRef}
+ style={{
+ transition: this.props.dataTransition,
+ position: this.props.Document.isInkMask ? 'absolute' : undefined,
+ transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
+ width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
+ height:
+ isButton || this.props.forceAutoHeight
+ ? undefined
+ : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
+ }}>
+ <DocumentViewInternal
+ {...this.props}
+ DocumentView={this.selfView}
+ viewPath={this.docViewPathFunc}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
+ NativeWidth={this.NativeWidth}
+ NativeHeight={this.NativeHeight}
+ NativeDimScaling={this.NativeDimScaling}
+ isSelected={this.isSelected}
+ select={this.select}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ focus={this.props.focus || emptyFunction}
+ ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
+ />
+ </div>
+ )}
+ </div>
+ );
}
}
+export function deiconifyViewFunc(documentView: DocumentView) {
+ documentView.iconify();
+ //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
+}
+ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
+ documentView.iconify();
+ documentView.select(false);
+});
+
ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
- if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
- else dv.switchViews(true, detailLayoutKeySuffix);
-}); \ No newline at end of file
+ if (dv.Document.layoutKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout');
+ else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
+});
+
+ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
+ const linkSource = Cast(linkCollection.linkSource, Doc, null);
+ const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
+ let wid = linkSource[WidthSym]();
+ const links = DocListCast(linkSource.links);
+ links.forEach(link => {
+ const other = LinkManager.getOppositeAnchor(link, linkSource);
+ const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
+ if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
+ const alias = Doc.MakeAlias(otherdoc);
+ alias.x = wid;
+ alias.y = 0;
+ alias._lockedPosition = false;
+ wid += otherdoc[WidthSym]();
+ Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', alias);
+ }
+ });
+ return links;
+});
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index 6c9d53d10..9714e1bd0 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -1,3 +1,8 @@
+@import "../global/globalCssVariables.scss";
+
.equationBox-cont {
transform-origin: top left;
+ > span {
+ width: 100%;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index c170f9867..0bd30bce9 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -12,11 +12,12 @@ import { LightboxView } from '../LightboxView';
import './EquationBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
-
@observer
export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); }
- public static SelectOnLoad: string = "";
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(EquationBox, fieldKey);
+ }
+ public static SelectOnLoad: string = '';
_ref: React.RefObject<EquationEditor> = React.createRef();
componentDidMount() {
if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
@@ -25,75 +26,92 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._ref.current!.mathField.focus();
this._ref.current!.mathField.select();
}
- reaction(() => StrCast(this.dataDoc.text),
+ reaction(
+ () => StrCast(this.dataDoc.text),
text => {
if (text && text !== this._ref.current!.mathField.latex()) {
this._ref.current!.mathField.latex(text);
}
- });
- reaction(() => this.props.isSelected(),
+ }
+ );
+ reaction(
+ () => this.props.isSelected(),
selected => {
if (this._ref.current) {
- if (selected) this._ref.current.element.current.children[0].addEventListener("keydown", this.keyPressed, true);
- else this._ref.current.element.current.children[0].removeEventListener("keydown", this.keyPressed);
+ if (selected) this._ref.current.element.current.children[0].addEventListener('keydown', this.keyPressed, true);
+ else this._ref.current.element.current.children[0].removeEventListener('keydown', this.keyPressed);
}
- }, { fireImmediately: true });
+ },
+ { fireImmediately: true }
+ );
}
plot: any;
@action
keyPressed = (e: KeyboardEvent) => {
- const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace("px", ""));
- const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace("px", ""));
- if (e.key === "Enter") {
+ const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', ''));
+ const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace('px', ''));
+ if (e.key === 'Enter') {
const nextEq = Docs.Create.EquationDocument({
- title: "# math", text: StrCast(this.dataDoc.text), _width, _height: 25,
- x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10
+ title: '# math',
+ text: StrCast(this.dataDoc.text),
+ _width,
+ _height: 25,
+ x: NumCast(this.layoutDoc.x),
+ y: NumCast(this.layoutDoc.y) + _height + 10,
});
EquationBox.SelectOnLoad = nextEq[Id];
this.props.addDocument?.(nextEq);
e.stopPropagation();
-
}
- if (e.key === "Tab") {
+ if (e.key === 'Tab') {
const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
x: NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym](),
y: NumCast(this.layoutDoc.y),
- _width: 400, _height: 300, backgroundColor: "white"
+ _width: 400,
+ _height: 300,
+ backgroundColor: 'white',
});
this.props.addDocument?.(graph);
e.stopPropagation();
}
- if (e.key === "Backspace" && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc);
- }
+ if (e.key === 'Backspace' && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc);
+ };
onChange = (str: string) => {
this.dataDoc.text = str;
const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
if (style) {
- const _height = Number(style.height.replace("px", ""));
- const _width = Number(style.width.replace("px", ""));
+ const _height = Number(style.height.replace('px', ''));
+ const _width = Number(style.width.replace('px', ''));
this.layoutDoc._width = Math.max(35, _width);
this.layoutDoc._height = Math.max(25, _height);
}
- }
+ };
render() {
TraceMobx();
- const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
- return (<div className="equationBox-cont"
- onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
- style={{
- transform: `scale(${scale})`,
- width: `${100 / scale}%`,
- height: `${100 / scale}%`,
- pointerEvents: !this.props.isSelected() ? "none" : undefined,
- }}
- onKeyDown={e => e.stopPropagation()}
- >
- <EquationEditor ref={this._ref}
- value={this.dataDoc.text || "x"}
- spaceBehavesLikeTab={true}
- onChange={this.onChange}
- autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
- autoOperatorNames="sin cos tan" />
- </div>);
+ const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+ return (
+ <div
+ ref={r => {
+ r instanceof HTMLDivElement &&
+ new ResizeObserver(
+ action((entries: any) => {
+ if (entries[0].contentBoxSize[0].inlineSize) {
+ this.rootDoc._width = entries[0].contentBoxSize[0].inlineSize;
+ }
+ })
+ ).observe(r);
+ }}
+ className="equationBox-cont"
+ onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
+ style={{
+ transform: `scale(${scale})`,
+ width: 'fit-content', // `${100 / scale}%`,
+ height: `${100 / scale}%`,
+ pointerEvents: !this.props.isSelected() ? 'none' : undefined,
+ }}
+ onKeyDown={e => e.stopPropagation()}>
+ <EquationEditor ref={this._ref} value={this.dataDoc.text || 'x'} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 943b9f153..dd2c13391 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,16 +1,17 @@
-import React = require("react");
-import { computed } from "mobx";
-import { observer } from "mobx-react";
-import { DateField } from "../../../fields/DateField";
-import { Doc, Field, FieldResult } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { WebField } from "../../../fields/URLField";
-import { DocumentViewSharedProps } from "./DocumentView";
+import React = require('react');
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { DateField } from '../../../fields/DateField';
+import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { ScriptField } from '../../../fields/ScriptField';
+import { WebField } from '../../../fields/URLField';
+import { DocumentViewSharedProps } from './DocumentView';
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
// However, that only happens because the properties are "defined" in the markup for the field view.
-// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc.
+// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc.
//
export interface FieldViewProps extends DocumentViewSharedProps {
// FieldView specific props that are not part of DocumentView props
@@ -21,11 +22,13 @@ export interface FieldViewProps extends DocumentViewSharedProps {
isContentActive: (outsideReaction?: boolean) => boolean | undefined;
isDocumentActive?: () => boolean;
isSelected: (outsideReaction?: boolean) => boolean;
- scaling?: () => number;
- setHeight: (height: number) => void;
+ setHeight?: (height: number) => void;
+ NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
+ onBrowseClick?: () => ScriptField | undefined;
+ onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
// properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
fontSize?: number;
height?: number;
width?: number;
@@ -38,7 +41,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
@observer
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string) {
- return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
}
@computed
@@ -71,23 +74,22 @@ export class FieldView extends React.Component<FieldViewProps> {
//}
else if (field instanceof DateField) {
return <p>{field.date.toLocaleString()}</p>;
- }
- else if (field instanceof Doc) {
- return <p><b>{field.title?.toString()}</b></p>;
- }
- else if (field instanceof List) {
- return <div> {field.length ? field.map(f => Field.toString(f)).join(", ") : ""} </div>;
+ } else if (field instanceof Doc) {
+ return (
+ <p>
+ <b>{field.title?.toString()}</b>
+ </p>
+ );
+ } else if (field instanceof List) {
+ return <div> {field.length ? field.map(f => Field.toString(f)).join(', ') : ''} </div>;
}
// bcz: this belongs here, but it doesn't render well so taking it out for now
else if (field instanceof WebField) {
return <p>{Field.toString(field.url.href)}</p>;
- }
- else if (!(field instanceof Promise)) {
+ } else if (!(field instanceof Promise)) {
return <p>{Field.toString(field)}</p>;
- }
- else {
- return <p> {"Waiting for server..."} </p>;
+ } else {
+ return <p> {'Waiting for server...'} </p>;
}
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index ba65acee0..dc3fc0396 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -1,52 +1,80 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import Select from "react-select";
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { RichTextField } from "../../../fields/RichTextField";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { UserOptions } from "../../util/GroupManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SelectionManager } from "../../util/SelectionManager";
-import { CollectionTreeView } from "../collections/CollectionTreeView";
-import { CollectionView } from "../collections/CollectionView";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { EditableView } from "../EditableView";
-import { SearchBox } from "../search/SearchBox";
-import { DashboardToggleButton, DefaultStyleProvider, StyleProp } from "../StyleProvider";
-import { DocumentViewProps } from "./DocumentView";
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import Select from 'react-select';
+import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { RichTextField } from '../../../fields/RichTextField';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { Docs, DocumentOptions } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { UserOptions } from '../../util/GroupManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { CollectionTreeView } from '../collections/CollectionTreeView';
+import { CollectionView } from '../collections/CollectionView';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { EditableView } from '../EditableView';
+import { SearchBox } from '../search/SearchBox';
+import { DashboardToggleButton, DefaultStyleProvider, StyleProp } from '../StyleProvider';
+import { DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import './FilterBox.scss';
-const higflyout = require("@hig/flyout");
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@observer
export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
-
constructor(props: Readonly<FieldViewProps>) {
super(props);
const targetDoc = FilterBox.targetDoc;
- if (targetDoc && !targetDoc.currentFilter) CurrentUserUtils.setupFilterDocs(targetDoc);
+ if (targetDoc && !targetDoc.currentFilter) targetDoc.currentFilter = FilterBox.createFilterDoc();
+ }
+
+ /// creates a new, empty filter doc
+ static createFilterDoc() {
+ const clearAll = `getProto(self).data = new List([])`;
+ const reqdOpts: DocumentOptions = {
+ _lockedPosition: true,
+ _autoHeight: true,
+ _fitWidth: true,
+ _height: 150,
+ _xPadding: 5,
+ _yPadding: 5,
+ _gridGap: 5,
+ _forceActive: true,
+ title: 'Unnamed Filter',
+ filterBoolean: 'AND',
+ boxShadow: '0 0',
+ childDontRegisterViews: true,
+ targetDropAction: 'same',
+ ignoreClick: true,
+ system: true,
+ childDropAction: 'none',
+ treeViewHideTitle: true,
+ treeViewTruncateTitleWidth: 150,
+ childContextMenuLabels: new List<string>(['Clear All']),
+ childContextMenuScripts: new List<ScriptField>([ScriptField.MakeFunction(clearAll)!]),
+ };
+ return Docs.Create.FilterDocument(reqdOpts);
+ }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(FilterBox, fieldKey);
}
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); }
public _filterSelected = false;
- public _filterMatch = "matched";
+ public _filterMatch = 'matched';
@computed static get currentContainingCollectionDoc() {
let docView: any = SelectionManager.Views()[0];
let doc = docView.Document;
- while (docView && doc && doc.type !== "collection") {
+ while (docView && doc && doc.type !== 'collection') {
doc = docView.props.ContainingCollectionDoc;
docView = docView.props.ContainingCollectionView;
}
@@ -58,7 +86,6 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
// * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
// */
-
// trying to get this to work rather than the version below this
// @computed static get targetDoc() {
@@ -70,57 +97,59 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
// // return FilterBox._filterScope === "Current Collection" ? SelectionManager.Views()[0].Document || CollectionDockingView.Instance.props.Document : CollectionDockingView.Instance.props.Document;
// }
-
/**
- * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
- */
+ * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
+ */
@computed static get targetDoc() {
- return SelectionManager.Docs().length ? SelectionManager.Docs()[0] : CurrentUserUtils.ActiveDashboard;
+ return SelectionManager.Docs().length ? SelectionManager.Docs()[0] : Doc.ActiveDashboard;
}
@computed static get targetDocChildKey() {
if (SelectionManager.Views().length) {
- return SelectionManager.Views()[0].ComponentView?.annotationKey || SelectionManager.Views()[0].ComponentView?.fieldKey || "data";
+ return SelectionManager.Views()[0].ComponentView?.annotationKey || SelectionManager.Views()[0].ComponentView?.fieldKey || 'data';
}
- return "data";
+ return 'data';
}
@computed static get targetDocChildren() {
- return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard.data);
+ return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || Doc.ActiveDashboard?.data);
}
@observable _loaded = false;
componentDidMount() {
- reaction(() => DocListCastAsync(this.layoutDoc.data),
- async (activeTabsAsync) => {
+ reaction(
+ () => DocListCastAsync(this.layoutDoc[this.fieldKey]),
+ async activeTabsAsync => {
const activeTabs = await activeTabsAsync;
activeTabs && (await SearchBox.foreachRecursiveDocAsync(activeTabs, emptyFunction));
- runInAction(() => this._loaded = true);
- }, { fireImmediately: true });
+ runInAction(() => (this._loaded = true));
+ },
+ { fireImmediately: true }
+ );
}
- @observable _allDocs = [] as Doc[];
+
@computed get allDocs() {
// trace();
+ const allDocs = new Set<Doc>();
const targetDoc = FilterBox.targetDoc;
if (this._loaded && targetDoc) {
- const allDocs = new Set<Doc>();
- const activeTabs = FilterBox.targetDocChildren;
- SearchBox.foreachRecursiveDoc(activeTabs, (depth, doc) => allDocs.add(doc));
- setTimeout(action(() => this._allDocs = Array.from(allDocs)));
+ SearchBox.foreachRecursiveDoc(FilterBox.targetDocChildren, (depth, doc) => allDocs.add(doc));
}
- return this._allDocs;
+ return Array.from(allDocs);
}
@computed get _allFacets() {
// trace();
- const noviceReqFields = ["author", "tags", "text", "type"];
- const noviceLayoutFields: string[] = [];//["_curPage"];
+ const noviceReqFields = ['author', 'tags', 'text', 'type'];
+ const noviceLayoutFields: string[] = []; //["_curPage"];
const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
const keys = new Set<string>(noviceFields);
this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
- return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.UserDoc().noviceMode).sort();
+ return Array.from(keys.keys())
+ .filter(key => key[0])
+ .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
+ .sort();
}
-
/**
* The current attributes selected to filter based on
*/
@@ -138,26 +167,26 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
gatherFieldValues(childDocs: Doc[], facetKey: string) {
const valueSet = new Set<string>();
let rtFields = 0;
- childDocs.forEach((d) => {
+ childDocs.forEach(d => {
const facetVal = d[facetKey];
if (facetVal instanceof RichTextField) rtFields++;
valueSet.add(Field.toString(facetVal as Field));
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
- const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const data = d[annos ? fieldKey + '-annotations' : fieldKey];
if (data !== undefined) {
let subDocs = DocListCast(data);
if (subDocs.length > 0) {
let newarray: Doc[] = [];
while (subDocs.length > 0) {
newarray = [];
- subDocs.forEach((t) => {
+ subDocs.forEach(t => {
const facetVal = t[facetKey];
if (facetVal instanceof RichTextField) rtFields++;
facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
const fieldKey = Doc.LayoutFieldKey(t);
- const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView");
- DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc));
+ const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
+ DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
});
subDocs = newarray;
}
@@ -166,36 +195,39 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
return { strings: Array.from(valueSet.keys()), rtFields };
}
- removeFilterDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false;
+ removeFilterDoc = (doc: Doc | Doc[]) => ((doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false);
public removeFilter = (filterName: string) => {
const targetDoc = FilterBox.targetDoc;
- const filterDoc = targetDoc.currentFilter as Doc;
- const attributes = DocListCast(filterDoc.data);
- const found = attributes.findIndex(doc => doc.title === filterName);
- if (found !== -1) {
- (filterDoc.data as List<Doc>).splice(found, 1);
- const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
- if (docFilter) {
- let index: number;
- while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
- docFilter.splice(index, 1);
+ if (targetDoc) {
+ const filterDoc = targetDoc.currentFilter as Doc;
+ const attributes = DocListCast(filterDoc.data);
+ const found = attributes.findIndex(doc => doc.title === filterName);
+ if (found !== -1) {
+ (filterDoc.data as List<Doc>).splice(found, 1);
+ const docFilter = Cast(targetDoc._docFilters, listSpec('string'));
+ if (docFilter) {
+ let index: number;
+ while ((index = docFilter.findIndex(item => item.split(':')[0] === filterName)) !== -1) {
+ docFilter.splice(index, 1);
+ }
}
- }
- const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string"));
- if (docRangeFilters) {
- let index: number;
- while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
- docRangeFilters.splice(index, 3);
+ const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec('string'));
+ if (docRangeFilters) {
+ let index: number;
+ while ((index = docRangeFilters.findIndex(item => item.split(':')[0] === filterName)) !== -1) {
+ docRangeFilters.splice(index, 3);
+ }
}
}
}
return true;
- }
+ };
/**
* Responds to clicking the check box in the flyout menu
*/
facetClick = (facetHeader: string) => {
const { targetDoc, targetDocChildren } = FilterBox;
+ if (!targetDoc) return;
const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader);
if (found !== -1) {
this.removeFilter(facetHeader);
@@ -204,49 +236,68 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
const facetValues = this.gatherFieldValues(targetDocChildren, facetHeader);
let nonNumbers = 0;
- let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+ let minVal = Number.MAX_VALUE,
+ maxVal = -Number.MAX_VALUE;
facetValues.strings.map(val => {
const num = val ? Number(val) : Number.NaN;
if (Number.isNaN(num)) {
- nonNumbers++;
+ val && nonNumbers++;
} else {
minVal = Math.min(num, minVal);
maxVal = Math.max(num, maxVal);
}
});
let newFacet: Opt<Doc>;
- if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) {
- newFacet = Docs.Create.TextDocument("", {
- title: facetHeader, system: true, target: targetDoc, _width: 100, _height: 25, _stayInCollection: true,
- treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true
+ if (facetHeader === 'text' || facetValues.rtFields / allCollectionDocs.length > 0.1) {
+ newFacet = Docs.Create.TextDocument('', {
+ title: facetHeader,
+ system: true,
+ target: targetDoc,
+ _width: 100,
+ _height: 25,
+ _stayInCollection: true,
+ treeViewExpandedView: 'layout',
+ _treeViewOpen: true,
+ _forceActive: true,
+ ignoreClick: true,
});
+ Doc.GetProto(newFacet).forceActive = true; // required for FormattedTextBox to be able to gain focus since it will never be 'selected'
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
newFacet._textBoxPaddingX = newFacet._textBoxPaddingY = 4;
const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
- newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
- } else if (facetHeader !== "tags" && nonNumbers / facetValues.strings.length < .1) {
+ newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: 'string' });
+ } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
newFacet = Docs.Create.SliderDocument({
- title: facetHeader, system: true, target: targetDoc, _fitWidth: true, _height: 40, _stayInCollection: true,
- treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, _overflow: "visible",
+ title: facetHeader,
+ system: true,
+ target: targetDoc,
+ _fitWidth: true,
+ _height: 40,
+ _stayInCollection: true,
+ treeViewExpandedView: 'layout',
+ _treeViewOpen: true,
+ _forceActive: true,
+ _overflow: 'visible',
});
const newFacetField = Doc.LayoutFieldKey(newFacet);
const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader);
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * .1));
- const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * .05)));
- newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
- newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1];
- Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal;
- Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal;
+ const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
+ const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
+ newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0];
+ newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1];
+ Doc.GetProto(newFacet)[newFacetField + '-minThumb'] = extendedMinVal;
+ Doc.GetProto(newFacet)[newFacetField + '-maxThumb'] = extendedMaxVal;
const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`;
- newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+ newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: 'number' });
+ newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
} else {
newFacet = new Doc();
newFacet.system = true;
newFacet.title = facetHeader;
newFacet.treeViewOpen = true;
- newFacet.layout = CollectionView.LayoutString("data");
- newFacet.layoutKey = "layout";
+ newFacet.layout = CollectionView.LayoutString('data');
+ newFacet.layoutKey = 'layout';
newFacet.type = DocumentType.COL;
newFacet.target = targetDoc;
newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
@@ -254,11 +305,11 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
newFacet.hideContextMenu = true;
Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
}
- }
+ };
@computed get scriptField() {
- const scriptText = "setDocFilter(this?.target, heading, this.title, checked)";
- const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ const scriptText = 'setDocFilter(this?.target, heading, this.title, checked)';
+ const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: 'string', checked: 'string', containingTreeView: Doc.name });
return script ? () => script : undefined;
}
@@ -267,8 +318,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
@action
changeBool = (e: any) => {
- (FilterBox.targetDoc.currentFilter as Doc).filterBoolean = e.currentTarget.value;
- }
+ FilterBox.targetDoc && (DocCast(FilterBox.targetDoc.currentFilter).filterBoolean = e.currentTarget.value);
+ };
/**
* Changes whether to select matched or unmatched documents
@@ -276,7 +327,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
changeMatch = (e: any) => {
this._filterMatch = e.currentTarget.value;
- }
+ };
@action
changeSelected = () => {
@@ -287,151 +338,149 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._filterSelected = true;
// helper method to select specified docs
}
- }
+ };
FilteringStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
- switch (property.split(":")[0]) {
+ switch (property.split(':')[0]) {
case StyleProp.Decorations:
if (doc && !doc.treeViewHideHeaderFields) {
- return <>
- <div style={{ marginRight: "5px", fontSize: "10px" }}>
- <select className="filterBox-selection">
- <option value="Is" key="Is">Is</option>
- <option value="Is Not" key="Is Not">Is Not</option>
- </select>
- </div>
- <div className="filterBox-treeView-close" onClick={e => this.removeFilter(StrCast(doc.title))}>
- <FontAwesomeIcon icon={"times"} size="sm" />
- </div>
- </>;
+ return (
+ <>
+ <div style={{ marginRight: '5px', fontSize: '10px' }}>
+ <select className="filterBox-selection">
+ <option value="Is" key="Is">
+ Is
+ </option>
+ <option value="Is Not" key="Is Not">
+ Is Not
+ </option>
+ </select>
+ </div>
+ <div className="filterBox-treeView-close" onClick={e => this.removeFilter(StrCast(doc.title))}>
+ <FontAwesomeIcon icon={'times'} size="sm" />
+ </div>
+ </>
+ );
}
}
return DefaultStyleProvider(doc, props, property);
}
- suppressChildClick = () => ScriptField.MakeScript("")!;
+ suppressChildClick = () => ScriptField.MakeScript('')!;
/**
* Adds a filterDoc to the list of saved filters
*/
saveFilter = () => {
- Doc.AddDocToList(Doc.UserDoc(), "savedFilters", this.props.Document);
- }
+ Doc.AddDocToList(Doc.UserDoc(), 'savedFilters', this.props.Document);
+ };
/**
* Changes the title of the filterDoc
*/
onTitleValueChange = (val: string) => {
- this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc.title}`;
+ this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc?.title}`;
return true;
- }
+ };
/**
* The flyout from which you can select a saved filter to apply
*/
@computed get flyoutPanel() {
return DocListCast(Doc.UserDoc().savedFilters).map(doc => {
- return <>
- <div className="filterBox-tempFlyout" onWheel={e => e.stopPropagation()} style={{ height: 50, border: "2px" }} onPointerDown={() => this.props.updateFilterDoc?.(doc)}>
- {StrCast(doc.title)}
- </div>
- </>;
- }
- );
+ return (
+ <>
+ <div className="filterBox-tempFlyout" onWheel={e => e.stopPropagation()} style={{ height: 50, border: '2px' }} onPointerDown={() => this.props.updateFilterDoc?.(doc)}>
+ {StrCast(doc.title)}
+ </div>
+ </>
+ );
+ });
}
setTreeHeight = (hgt: number) => {
this.layoutDoc._height = NumCast(this.layoutDoc._autoHeightMargins) + 150; // need to add all the border sizes together.
- }
+ };
/**
* add lock and hide button decorations for the "Dashboards" flyout TreeView
*/
FilterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
- if (property.split(":")[0] === StyleProp.Decorations) {
- return !doc || doc.treeViewHideHeaderFields ? (null) :
- DashboardToggleButton(doc, "hidden", "trash", "trash", () => this.removeFilter(StrCast(doc.title)));
+ if (property.split(':')[0] === StyleProp.Decorations) {
+ return !doc || doc.treeViewHideHeaderFields ? null : DashboardToggleButton(doc, 'hidden', 'trash', 'trash', () => this.removeFilter(StrCast(doc.title)));
}
return this.props.styleProvider?.(doc, props, property);
- }
+ };
layoutHeight = () => this.layoutDoc[HeightSym]();
render() {
const facetCollection = this.props.Document;
const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
- return this.props.dontRegisterView ? (null) : <div className="filterBox-treeView" style={{ width: "100%" }}>
-
- <div className="filterBox-title">
- <EditableView
- key="editableView"
- contents={this.props.Document.title}
- height={24}
- fontSize={15}
- GetValue={() => StrCast(this.props.Document.title)}
- SetValue={this.onTitleValueChange}
- />
- </div>
+ return this.props.dontRegisterView ? null : (
+ <div className="filterBox-treeView" style={{ width: '100%' }}>
+ <div className="filterBox-title">
+ <EditableView key="editableView" contents={this.props.Document.title} height={24} fontSize={15} GetValue={() => StrCast(this.props.Document.title)} SetValue={this.onTitleValueChange} />
+ </div>
- <div className="filterBox-select-bool">
- <select className="filterBox-selection" onChange={this.changeBool} defaultValue={StrCast((FilterBox.targetDoc.currentFilter as Doc)?.filterBoolean)}>
- <option value="AND" key="AND">AND</option>
- <option value="OR" key="OR">OR</option>
- </select>
- <div className="filterBox-select-text">filters together</div>
- </div>
+ <div className="filterBox-select-bool">
+ <select className="filterBox-selection" onChange={this.changeBool} defaultValue={StrCast((FilterBox.targetDoc?.currentFilter as Doc)?.filterBoolean)}>
+ <option value="AND" key="AND">
+ AND
+ </option>
+ <option value="OR" key="OR">
+ OR
+ </option>
+ </select>
+ <div className="filterBox-select-text">filters together</div>
+ </div>
- <div className="filterBox-select">
- <Select
- placeholder="Add a filter..."
- options={options}
- isMulti={false}
- onChange={val => this.facetClick((val as UserOptions).value)}
- value={null}
- closeMenuOnSelect={true}
- />
- </div>
+ <div className="filterBox-select">
+ <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
+ </div>
- <div className="filterBox-tree" key="tree">
- <CollectionTreeView
- Document={facetCollection}
- DataDoc={Doc.GetProto(facetCollection)}
- fieldKey={this.props.fieldKey}
- CollectionView={undefined}
- disableDocBrushing={true}
- setHeight={this.setTreeHeight} // if the tree view can trigger the height of the filter box to change, then this needs to be filled in.
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.layoutHeight}
- rootSelected={this.props.rootSelected}
- renderDepth={1}
- dropAction={this.props.dropAction}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- isAnyChildContentActive={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- isSelected={returnFalse}
- select={returnFalse}
- bringToFront={emptyFunction}
- isContentActive={returnTrue}
- whenChildContentsActiveChanged={returnFalse}
- treeViewHideTitle={true}
- focus={returnFalse}
- treeViewHideHeaderFields={false}
- dontRegisterView={true}
- styleProvider={this.FilterStyleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={this.props.docViewPath}
- scriptContext={this.props.scriptContext}
- moveDocument={returnFalse}
- removeDocument={this.removeFilterDoc}
- addDocument={returnFalse} />
- </div>
- <div className="filterBox-bottom">
- {/* <div className="filterBox-select-matched">
+ <div className="filterBox-tree" key="tree">
+ <CollectionTreeView
+ Document={facetCollection}
+ DataDoc={Doc.GetProto(facetCollection)}
+ fieldKey={this.props.fieldKey}
+ CollectionView={undefined}
+ disableDocBrushing={true}
+ setHeight={this.setTreeHeight} // if the tree view can trigger the height of the filter box to change, then this needs to be filled in.
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ childDocumentsActive={returnTrue}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.layoutHeight}
+ rootSelected={this.props.rootSelected}
+ renderDepth={1}
+ dropAction={this.props.dropAction}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ isAnyChildContentActive={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ isSelected={returnFalse}
+ select={returnFalse}
+ bringToFront={emptyFunction}
+ isContentActive={returnTrue}
+ whenChildContentsActiveChanged={returnFalse}
+ treeViewHideTitle={true}
+ focus={returnFalse}
+ onCheckedClick={this.scriptField}
+ treeViewHideHeaderFields={false}
+ dontRegisterView={true}
+ styleProvider={this.FilterStyleProvider}
+ docViewPath={this.props.docViewPath}
+ scriptContext={this.props.scriptContext}
+ moveDocument={returnFalse}
+ removeDocument={this.removeFilterDoc}
+ addDocument={returnFalse}
+ />
+ </div>
+ <div className="filterBox-bottom">
+ {/* <div className="filterBox-select-matched">
<input className="filterBox-select-box" type="checkbox"
onChange={this.changeSelected} />
<div className="filterBox-select-text">select</div>
@@ -442,57 +491,91 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="filterBox-select-text">documents</div>
</div> */}
- <div style={{ display: "flex" }}>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark"
- onPointerDown={this.saveFilter}
- >
- <div>SAVE</div>
+ <div style={{ display: 'flex' }}>
+ <div className="filterBox-saveWrapper">
+ <div className="filterBox-saveBookmark" onPointerDown={this.saveFilter}>
+ <div>SAVE</div>
+ </div>
</div>
- </div>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark">
- <Flyout className="myFilters-flyout" anchorPoint={anchorPoints.TOP} content={this.flyoutPanel}>
- <div>FILTERS</div>
- </Flyout>
+ <div className="filterBox-saveWrapper">
+ <div className="filterBox-saveBookmark">
+ <Flyout className="myFilters-flyout" anchorPoint={anchorPoints.TOP} content={this.flyoutPanel}>
+ <div>FILTERS</div>
+ </Flyout>
+ </div>
</div>
- </div>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark"
- onPointerDown={this.props.createNewFilterDoc}
- >
- <div>NEW</div>
+ <div className="filterBox-saveWrapper">
+ <div className="filterBox-saveBookmark" onPointerDown={this.props.createNewFilterDoc}>
+ <div>NEW</div>
+ </div>
</div>
</div>
</div>
</div>
- </div>;
+ );
}
}
ScriptingGlobals.add(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
- const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []);
+ const docFilters = Cast(layoutDoc._docFilters, listSpec('string'), []);
for (const filter of docFilters) {
- const fields = filter.split(":"); // split into key:value:modifiers
+ const fields = filter.split(':'); // split into key:value:modifiers
if (fields[0] === facetHeader && fields[1] === facetValue) {
return fields[2];
}
}
return undefined;
});
+ScriptingGlobals.add(function readNumFacetData(layoutDoc: Doc, facetDoc: Doc, childKey: string, facetHeader: string) {
+ const allCollectionDocs = new Set<Doc>();
+ const activeTabs = DocListCast(layoutDoc[childKey]);
+ SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
+ const set = new Set<string>();
+ if (facetHeader === 'tags')
+ allCollectionDocs.forEach(child =>
+ Field.toString(child[facetHeader] as Field)
+ .split(':')
+ .forEach(key => set.add(key))
+ );
+ else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
+ const facetValues = Array.from(set).filter(v => v);
+
+ let minVal = Number.MAX_VALUE,
+ maxVal = -Number.MAX_VALUE;
+ facetValues.map(val => {
+ const num = val ? Number(val) : Number.NaN;
+ if (!Number.isNaN(num)) {
+ minVal = Math.min(num, minVal);
+ maxVal = Math.max(num, maxVal);
+ }
+ });
+ const newFacetField = Doc.LayoutFieldKey(facetDoc);
+ const ranged = Doc.readDocRangeFilter(layoutDoc, facetHeader);
+ const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
+ const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
+ facetDoc[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0];
+ facetDoc[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1];
+ Doc.GetProto(facetDoc)[newFacetField + '-minThumb'] = extendedMinVal;
+ Doc.GetProto(facetDoc)[newFacetField + '-maxThumb'] = extendedMaxVal;
+});
ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) {
const allCollectionDocs = new Set<Doc>();
const activeTabs = DocListCast(layoutDoc[childKey]);
SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
const set = new Set<string>();
- if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key)));
+ if (facetHeader === 'tags')
+ allCollectionDocs.forEach(child =>
+ Field.toString(child[facetHeader] as Field)
+ .split(':')
+ .forEach(key => set.add(key))
+ );
else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
const facetValues = Array.from(set).filter(v => v);
let nonNumbers = 0;
facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
- const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
+ const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
const doc = new Doc();
doc.system = true;
doc.title = facetValue.toString();
@@ -500,8 +583,8 @@ ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, fa
doc.facetHeader = facetHeader;
doc.facetValue = facetValue;
doc.treeViewHideHeaderFields = true;
- doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(self.target, self.facetHeader, self.facetValue)");
+ doc.treeViewChecked = ComputedField.MakeFunction('determineCheckedState(self.target, self.facetHeader, self.facetValue)');
return doc;
});
return new List<Doc>(facetValueDocSet);
-}); \ No newline at end of file
+});
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 3ab0a3ff2..15d0f88f6 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -1,4 +1,4 @@
-import functionPlot from "function-plot";
+import functionPlot from 'function-plot';
import { action, computed, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -12,7 +12,6 @@ import { Docs } from '../../documents/Documents';
import { ViewBoxBaseComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
-
const EquationSchema = createSchema({});
type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSchema]>;
@@ -20,74 +19,83 @@ const EquationDocument = makeInterface(EquationSchema, documentSchema);
@observer
export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FunctionPlotBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(FunctionPlotBox, fieldKey);
+ }
public static GraphCount = 0;
_plot: any;
- _plotId = "";
+ _plotId = '';
_plotEle: any;
constructor(props: any) {
super(props);
- this._plotId = "graph" + FunctionPlotBox.GraphCount++;
+ this._plotId = 'graph' + FunctionPlotBox.GraphCount++;
}
componentDidMount() {
this.props.setContentView?.(this);
- reaction(() => [DocListCast(this.dataDoc.data).lastElement()?.text, this.layoutDoc.width, this.layoutDoc.height, this.dataDoc.xRange, this.dataDoc.yRange],
- () => this.createGraph());
+ reaction(
+ () => [DocListCast(this.dataDoc[this.fieldKey]).lastElement()?.text, this.layoutDoc.width, this.layoutDoc.height, this.dataDoc.xRange, this.dataDoc.yRange],
+ () => this.createGraph()
+ );
}
getAnchor = () => {
const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc });
anchor.xRange = new List<number>(Array.from(this._plot.options.xAxis.domain));
anchor.yRange = new List<number>(Array.from(this._plot.options.yAxis.domain));
return anchor;
- }
+ };
@action
scrollFocus = (doc: Doc, smooth: boolean) => {
- this.dataDoc.xRange = new List<number>(Array.from(Cast(doc.xRange, listSpec("number"), Cast(this.dataDoc.xRange, listSpec("number"), [-10, 10]))));
- this.dataDoc.yRange = new List<number>(Array.from(Cast(doc.yRange, listSpec("number"), Cast(this.dataDoc.xRange, listSpec("number"), [-1, 9]))));
+ this.dataDoc.xRange = new List<number>(Array.from(Cast(doc.xRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10]))));
+ this.dataDoc.yRange = new List<number>(Array.from(Cast(doc.yRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9]))));
return 0;
- }
+ };
createGraph = (ele?: HTMLDivElement) => {
this._plotEle = ele || this._plotEle;
const width = this.props.PanelWidth();
const height = this.props.PanelHeight();
- const fn = StrCast(DocListCast(this.dataDoc.data).lastElement()?.text, "x^2").replace(/\\frac\{(.*)\}\{(.*)\}/, "($1/$2)");
+ const fn = StrCast(DocListCast(this.dataDoc.data).lastElement()?.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)');
try {
this._plot = functionPlot({
- target: "#" + this._plotEle.id,
+ target: '#' + this._plotEle.id,
width,
height,
- xAxis: { domain: Cast(this.dataDoc.xRange, listSpec("number"), [-10, 10]) },
- yAxis: { domain: Cast(this.dataDoc.xRange, listSpec("number"), [-1, 9]) },
+ xAxis: { domain: Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10]) },
+ yAxis: { domain: Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9]) },
grid: true,
data: [
{
fn,
// derivative: { fn: "2 * x", updateOnMouseMove: true }
- }
- ]
+ },
+ ],
});
} catch (e) {
console.log(e);
}
- }
+ };
@computed get theGraph() {
- return <div id={`${this._plotId}`} ref={r => r && this.createGraph(r)} style={{ position: "absolute", width: "100%", height: "100%" }}
- onPointerDown={e => e.stopPropagation()} />;
+ return <div id={`${this._plotId}`} ref={r => r && this.createGraph(r)} style={{ position: 'absolute', width: '100%', height: '100%' }} onPointerDown={e => e.stopPropagation()} />;
}
render() {
TraceMobx();
- return (<div
- style={{
- pointerEvents: !this.isContentActive() ? "all" : undefined,
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight()
- }}
- >
- {this.theGraph}
- <div style={{
- display: this.props.isSelected() ? "none" : undefined, position: "absolute", width: "100%", height: "100%",
- pointerEvents: "all"
- }} />
- </div>);
+ return (
+ <div
+ style={{
+ pointerEvents: !this.isContentActive() ? 'all' : undefined,
+ width: this.props.PanelWidth(),
+ height: this.props.PanelHeight(),
+ }}>
+ {this.theGraph}
+ <div
+ style={{
+ display: this.props.isSelected() ? 'none' : undefined,
+ position: 'absolute',
+ width: '100%',
+ height: '100%',
+ pointerEvents: 'all',
+ }}
+ />
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 4238f6d29..cd2b23f02 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -103,7 +103,7 @@
display: flex;
height: 100%;
- .imageBox-fadeBlocker {
+ .imageBox-fadeBlocker, .imageBox-fadeBlocker-hover{
width: 100%;
height: 100%;
position: absolute;
@@ -133,12 +133,10 @@
transition: opacity 1s ease-in-out;
}
-.imageBox:hover {
- .imageBox-fadeBlocker {
- -webkit-transition: opacity 1s ease-in-out;
- -moz-transition: opacity 1s ease-in-out;
- -o-transition: opacity 1s ease-in-out;
- transition: opacity 1s ease-in-out;
- opacity: 0;
- }
+.imageBox-fadeBlocker-hover {
+ -webkit-transition: opacity 1s ease-in-out;
+ -moz-transition: opacity 1s ease-in-out;
+ -o-transition: opacity 1s ease-in-out;
+ transition: opacity 1s ease-in-out;
+ opacity: 0;
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0a4168698..9590bcb15 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,7 +1,7 @@
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
-import { observer } from "mobx-react";
+import { observer } from 'mobx-react';
import { extname } from 'path';
-import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, Opt, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -14,11 +14,12 @@ import { TraceMobx } from '../../../fields/util';
import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
+import { DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
-import { ContextMenu } from "../../views/ContextMenu";
+import { ContextMenu } from '../../views/ContextMenu';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
@@ -27,62 +28,64 @@ import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { FaceRectangles } from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
-import "./ImageBox.scss";
-import React = require("react");
+import './ImageBox.scss';
+import React = require('react');
export const pageSchema = createSchema({
- googlePhotosUrl: "string",
- googlePhotosTags: "string"
+ googlePhotosUrl: 'string',
+ googlePhotosTags: 'string',
});
const uploadIcons = {
- idle: "downarrow.png",
- loading: "loading.gif",
- success: "greencheck.png",
- failure: "redx.png"
+ idle: 'downarrow.png',
+ loading: 'loading.gif',
+ success: 'greencheck.png',
+ failure: 'redx.png',
};
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
- protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
- private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
+ protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined;
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(ImageBox, fieldKey);
+ }
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
- @observable _curSuffix = "";
+ private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
+ @observable _curSuffix = '';
@observable _uploadIcon = uploadIcons.idle;
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
- }
- setViewSpec = (anchor: Doc, preview: boolean) => {
-
- } // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
-
+ };
+ setViewSpec = (anchor: Doc, preview: boolean) => {}; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
getAnchor = () => {
- const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations);
+ const anchor = this._getAnchor?.(this._savedAnnotations);
anchor && this.addDocument(anchor);
return anchor ?? this.rootDoc;
- }
+ };
componentDidMount() {
this.props.setContentView?.(this); // bcz: do not remove this. without it, stepping into an image in the lightbox causes an infinite loop....
- this._disposers.sizer = reaction(() => (
- {
+ this._disposers.sizer = reaction(
+ () => ({
forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes,
- scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0],
- selected: this.props.isSelected()
+ scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth,
+ selected: this.props.isSelected(),
}),
- ({ forceFull, scrSize, selected }) => this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o",
- { fireImmediately: true, delay: 1000 });
- this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
+ ({ forceFull, scrSize, selected }) => (this._curSuffix = this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 || !selected ? '_l' : '_o'),
+ { fireImmediately: true, delay: 1000 }
+ );
+ this._disposers.path = reaction(
+ () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
({ nativeSize, width }) => {
- if (!this.layoutDoc._height) {
- this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth;
+ if (true || !this.layoutDoc._height) {
+ this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
}
},
- { fireImmediately: true });
+ { fireImmediately: true }
+ );
}
componentWillUnmount() {
@@ -94,10 +97,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
if (de.metaKey) {
- de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => {
- Doc.AddDocToList(this.dataDoc, this.fieldKey + "-alternates", drop);
- e.stopPropagation();
- }));
+ de.complete.docDragData.droppedDocuments.forEach(
+ action((drop: Doc) => {
+ Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+ e.stopPropagation();
+ })
+ );
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
const targetField = Doc.LayoutFieldKey(layoutDoc);
@@ -110,69 +115,111 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
}
}
- }
+ };
@undoBatch
- resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes
+ resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes);
@undoBatch
rotate = action(() => {
- const nw = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
- const nh = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+ const nw = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']);
+ const nh = NumCast(this.dataDoc[this.fieldKey + '-nativeHeight']);
const w = this.layoutDoc._width;
const h = this.layoutDoc._height;
- this.dataDoc[this.fieldKey + "-rotation"] = (NumCast(this.dataDoc[this.fieldKey + "-rotation"]) + 90) % 360;
- this.dataDoc[this.fieldKey + "-nativeWidth"] = nh;
- this.dataDoc[this.fieldKey + "-nativeHeight"] = nw;
+ this.dataDoc[this.fieldKey + '-rotation'] = (NumCast(this.dataDoc[this.fieldKey + '-rotation']) + 90) % 360;
+ this.dataDoc[this.fieldKey + '-nativeWidth'] = nh;
+ this.dataDoc[this.fieldKey + '-nativeHeight'] = nw;
this.layoutDoc._width = h;
this.layoutDoc._height = w;
});
+ crop = (region: Doc | undefined, addCrop?: boolean) => {
+ if (!region) return;
+ const cropping = Doc.MakeCopy(region, true);
+ Doc.GetProto(region).lockedPosition = true;
+ Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
+ Doc.GetProto(region).isPushpin = true;
+ this.addDocument(region);
+ const anchx = NumCast(cropping.x);
+ const anchy = NumCast(cropping.y);
+ const anchw = NumCast(cropping._width);
+ const anchh = NumCast(cropping._height);
+ const viewScale = NumCast(this.rootDoc[this.fieldKey + '-nativeWidth']) / anchw;
+ cropping.title = 'crop: ' + this.rootDoc.title;
+ cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
+ cropping.y = NumCast(this.rootDoc.y);
+ cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
+ cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+ cropping.isLinkButton = undefined;
+ const croppingProto = Doc.GetProto(cropping);
+ croppingProto.annotationOn = undefined;
+ croppingProto.isPrototype = true;
+ croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ croppingProto.type = DocumentType.IMG;
+ croppingProto.layout = ImageBox.LayoutString('data');
+ croppingProto.data = ObjectField.MakeCopy(this.rootDoc[this.fieldKey] as ObjectField);
+ croppingProto['data-nativeWidth'] = anchw;
+ croppingProto['data-nativeHeight'] = anchh;
+ croppingProto.viewScale = viewScale;
+ croppingProto.viewScaleMin = viewScale;
+ croppingProto.panX = anchx / viewScale;
+ croppingProto.panY = anchy / viewScale;
+ croppingProto.panXMin = anchx / viewScale;
+ croppingProto.panXMax = anchw / viewScale;
+ croppingProto.panYMin = anchy / viewScale;
+ croppingProto.panYMax = anchh / viewScale;
+ if (addCrop) {
+ DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+ }
+ this.props.bringToFront(cropping);
+ return cropping;
+ };
+
specificContextMenu = (e: React.MouseEvent): void => {
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
- funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? "Dynamic Res" : "Full Res"}`, event: this.resolution, icon: "expand-arrows-alt" });
- funcs.push({ description: "Copy path", event: () => Utils.CopyText(this.choosePath(field.url)), icon: "expand-arrows-alt" });
- if (!Doc.UserDoc().noviceMode) {
- funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
-
- 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" });
+ funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'expand-arrows-alt' });
+ funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand-arrows-alt' });
+ funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'expand-arrows-alt' });
+ if (!Doc.noviceMode) {
+ funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
+
+ 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' });
}
- }
+ };
extractFaces = () => {
const converter = (results: any) => {
return results.map((face: CognitiveServices.Image.Face) => Doc.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
};
- this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-faces"], this.url, Service.Face, converter);
- }
+ this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-faces'], this.url, Service.Face, converter);
+ };
generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
const converter = (results: any) => {
- const tagDoc = new Doc;
+ const tagDoc = new Doc();
const tagsList = new List();
results.tags.map((tag: Tag) => {
tagsList.push(tag.name);
- const sanitized = tag.name.replace(" ", "_");
+ const sanitized = tag.name.replace(' ', '_');
tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
});
- this.dataDoc[this.fieldKey + "-generatedTags"] = tagsList;
- tagDoc.title = "Generated Tags Doc";
+ this.dataDoc[this.fieldKey + '-generatedTags'] = tagsList;
+ tagDoc.title = 'Generated Tags Doc';
tagDoc.confidence = threshold;
return tagDoc;
};
- this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-generatedTagsDoc"], this.url, Service.ComputerVision, converter);
- }
+ this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-generatedTagsDoc'], this.url, Service.ComputerVision, converter);
+ };
@computed private get url() {
const data = Cast(this.dataDoc[this.fieldKey], ImageField);
@@ -181,9 +228,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
choosePath(url: URL) {
const lower = url.href.toLowerCase();
- if (url.protocol === "data") return url.href;
+ if (url.protocol === 'data') return url.href;
if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href);
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return url.href; //Why is this here
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return url.href; //Why is this here
const ext = extname(url.href);
return url.href.replace(ext, this._curSuffix + ext);
@@ -191,40 +238,36 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
considerGooglePhotosLink = () => {
const remoteUrl = this.dataDoc.googlePhotosUrl;
- return !remoteUrl ? (null) : (<img draggable={false}
- style={{ transformOrigin: "bottom right" }}
- id={"google-photos"}
- src={"/assets/google_photos.png"}
- onClick={() => window.open(remoteUrl)}
- />);
- }
+ return !remoteUrl ? null : <img draggable={false} style={{ transformOrigin: 'bottom right' }} id={'google-photos'} src={'/assets/google_photos.png'} onClick={() => window.open(remoteUrl)} />;
+ };
considerGooglePhotosTags = () => {
const tags = this.dataDoc.googlePhotosTags;
- return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />);
- }
+ return !tags ? null : <img id={'google-tags'} src={'/assets/google_tags.png'} />;
+ };
@computed
private get considerDownloadIcon() {
const data = this.dataDoc[this.fieldKey];
if (!(data instanceof ImageField)) {
- return (null);
+ return null;
}
const primary = data.url.href;
if (primary.includes(window.location.origin)) {
- return (null);
+ return null;
}
return (
<img
- id={"upload-icon"} draggable={false}
- style={{ transformOrigin: "bottom right" }}
+ id={'upload-icon'}
+ draggable={false}
+ style={{ transformOrigin: 'bottom right' }}
src={`/assets/${this._uploadIcon}`}
onClick={async () => {
const { dataDoc } = this;
const { success, failure, idle, loading } = uploadIcons;
- runInAction(() => this._uploadIcon = loading);
- const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
- dataDoc[this.props.fieldKey + "-originalUrl"] = primary;
+ runInAction(() => (this._uploadIcon = loading));
+ const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [primary] });
+ dataDoc[this.props.fieldKey + '-originalUrl'] = primary;
let succeeded = true;
let data: ImageField | undefined;
try {
@@ -232,13 +275,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
} catch {
succeeded = false;
}
- runInAction(() => this._uploadIcon = succeeded ? success : failure);
- setTimeout(action(() => {
- this._uploadIcon = idle;
- if (data) {
- dataDoc[this.fieldKey] = data;
- }
- }), 2000);
+ runInAction(() => (this._uploadIcon = succeeded ? success : failure));
+ setTimeout(
+ action(() => {
+ this._uploadIcon = idle;
+ if (data) {
+ dataDoc[this.fieldKey] = data;
+ }
+ }),
+ 2000
+ );
}}
/>
);
@@ -246,18 +292,21 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get nativeSize() {
TraceMobx();
- const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 500);
- const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1);
- const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + "-nativeOrientation"], 1);
+ const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth'], 500));
+ const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '-nativeHeight'], 1));
+ const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '-nativeOrientation'], 1);
return { nativeWidth, nativeHeight, nativeOrientation };
}
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
- const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+ const alts = DocListCast(this.dataDoc[this.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const altpaths = alts
+ .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
+ .filter(url => url)
+ .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
- return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
+ return paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
}
@computed get content() {
@@ -266,51 +315,38 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const srcpath = this.paths[0];
const fadepath = this.paths[Math.min(1, this.paths.length - 1)];
const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize;
- const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]);
+ const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']);
const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
- let transformOrigin = "center center";
+ let transformOrigin = 'center center';
let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`;
if (rotation === 90 || rotation === -270) {
- transformOrigin = "top left";
+ transformOrigin = 'top left';
transform = `translate(100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
} else if (rotation === 180) {
transform = `rotate(${rotation}deg) scale(${aspect})`;
} else if (rotation === 270 || rotation === -90) {
- transformOrigin = "right top";
+ transformOrigin = 'right top';
transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
}
- return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
- <div className="imageBox-fader" style={{ overflow: Array.from(this.props.docViewPath?.()).slice(-1)[0].fitWidth ? "auto" : undefined }} >
- <img key="paths" ref={this._imgRef}
- src={srcpath}
- style={{ transform, transformOrigin }}
- draggable={false}
- width={nativeWidth} />
- {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker">
- <img className="imageBox-fadeaway" key={"fadeaway"} ref={this._imgRef}
- src={fadepath}
- style={{ transform, transformOrigin }} draggable={false}
- width={nativeWidth} />
- </div>}
+ return (
+ <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
+ <div className="imageBox-fader" style={{ overflow: Array.from(this.props.docViewPath?.()).slice(-1)[0].fitWidth ? 'auto' : undefined }}>
+ <img key="paths" src={srcpath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
+ {fadepath === srcpath ? null : (
+ <div className={`imageBox-fadeBlocker${this.props.isHovering?.() ? '-hover' : ''}`}>
+ <img className="imageBox-fadeaway" key={'fadeaway'} src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
+ </div>
+ )}
+ </div>
+ {this.considerDownloadIcon}
+ {this.considerGooglePhotosLink()}
+ <FaceRectangles document={this.dataDoc} color={'#0000FF'} backgroundColor={'#0000FF'} />
</div>
- {this.considerDownloadIcon}
- {this.considerGooglePhotosLink()}
- <FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
- </div>;
- }
-
- // adjust y position to center image in panel aspect is bigger than image aspect.
- // bcz :note, this is broken for rotated images
- get ycenter() {
- const { nativeWidth, nativeHeight } = this.nativeSize;
- const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]);
- const aspect = (rotation % 180) ? nativeWidth / nativeHeight : nativeHeight / nativeWidth;
- return this.props.PanelHeight() / this.props.PanelWidth() > aspect ?
- (this.props.PanelHeight() - this.props.PanelWidth() * aspect) / 2 : 0;
+ );
}
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.ycenter);
+ screenToLocalTransform = this.props.ScreenToLocalTransform;
contentFunc = () => [this.content];
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
@@ -322,63 +358,79 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
}
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
- setupMoveUpEvents(this, e, action(e => {
- MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeing = [e.clientX, e.clientY];
- return true;
- }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
+ if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale, 1) <= NumCast(this.rootDoc.viewScaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }),
+ returnFalse,
+ () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
+ false
+ );
}
- }
+ };
@action
finishMarquee = () => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeing = undefined;
this.props.select(false);
- }
+ };
+ savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
- const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / (this.props.scaling?.() || 1)}px` : borderRad;
- return (<div className="imageBox" onContextMenu={this.specificContextMenu} ref={this._mainCont}
- style={{
- width: this.props.PanelWidth() ? undefined : `100%`,
- height: this.props.PanelWidth() ? undefined : `100%`,
- pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
- borderRadius
- }} >
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- renderDepth={this.props.renderDepth + 1}
- fieldKey={this.annotationKey}
- CollectionView={undefined}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={true}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
- ScreenToLocalTransform={this.screenToLocalTransform}
- select={emptyFunction}
- scaling={returnOne}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}>
- {this.contentFunc}
- </CollectionFreeFormView>
- {this.annotationLayer}
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator
- rootDoc={this.rootDoc}
- scrollTop={0}
- down={this._marqueeing}
- scaling={this.props.scaling}
- docView={this.props.docViewPath().slice(-1)[0]}
- addDocument={this.addDocument}
- finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
- annotationLayer={this._annotationLayer.current}
- mainCont={this._mainCont.current}
- />}
- </div >);
+ const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this.props.NativeDimScaling?.() || 1)}px` : borderRad;
+ return (
+ <div
+ className="imageBox"
+ onContextMenu={this.specificContextMenu}
+ ref={this._mainCont}
+ style={{
+ width: this.props.PanelWidth() ? undefined : `100%`,
+ height: this.props.PanelWidth() ? undefined : `100%`,
+ pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
+ borderRadius,
+ }}>
+ <CollectionFreeFormView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ renderDepth={this.props.renderDepth + 1}
+ fieldKey={this.annotationKey}
+ CollectionView={undefined}
+ isAnnotationOverlay={true}
+ annotationLayerHostsContent={true}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.props.PanelHeight}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ select={emptyFunction}
+ NativeDimScaling={returnOne}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}>
+ {this.contentFunc}
+ </CollectionFreeFormView>
+ {this.annotationLayer}
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ <MarqueeAnnotator
+ rootDoc={this.rootDoc}
+ scrollTop={0}
+ down={this._marqueeing}
+ scaling={this.props.NativeDimScaling}
+ docView={this.props.docViewPath().slice(-1)[0]}
+ addDocument={this.addDocument}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ anchorMenuCrop={this.crop}
+ />
+ )}
+ </div>
+ );
}
-
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index c44c8c828..4b1fbaf7d 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -67,7 +67,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
const { script, type, onDelegate } = kvpScript;
//const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
- const target = forceOnDelegate || onDelegate ? doc : doc.proto || doc;
+ const target = forceOnDelegate || onDelegate || key.startsWith("_") ? doc : doc.proto || doc;
let field: Field;
if (type === "computed") {
field = new ComputedField(script);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 881cbf2bb..80def3025 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -60,7 +60,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
docRangeFilters: returnEmptyFilter,
searchFilterDocs: returnEmptyDoclist,
styleProvider: DefaultStyleProvider,
- layerProvider: undefined,
docViewPath: returnEmptyDoclist,
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js
index db43551d8..290152cd0 100644
--- a/src/client/views/nodes/LabelBigText.js
+++ b/src/client/views/nodes/LabelBigText.js
@@ -5,14 +5,14 @@ And from Jetroid/bigtext.js v1.0.0, September 2016
Usage:
BigText("#myElement",{
- rotateText: {Number}, (null)
- fontSizeFactor: {Number}, (0.8)
- maximumFontSize: {Number}, (null)
- limitingDimension: {String}, ("both")
- horizontalAlign: {String}, ("center")
- verticalAlign: {String}, ("center")
- textAlign: {String}, ("center")
- whiteSpace: {String}, ("nowrap")
+ rotateText: {Number}, (null)
+ fontSizeFactor: {Number}, (0.8)
+ maximumFontSize: {Number}, (null)
+ limitingDimension: {String}, ("both")
+ horizontalAlign: {String}, ("center")
+ verticalAlign: {String}, ("center")
+ textAlign: {String}, ("center")
+ whiteSpace: {String}, ("nowrap")
});
@@ -28,6 +28,8 @@ fontSizeFactor: This option is used to give some vertical spacing for letters th
maximumFontSize: maximum font size to use.
+minimumFontSize: minimum font size to use. if font is calculated smaller than this, text will be rendered at this size and wrapped
+
limitingDimension: In which dimension the font size should be limited. Possible values: "width", "height" or "both". Defaults to both. Using this option with values different than "both" overwrites the element parent width or height.
horizontalAlign: Where to align the text horizontally. Possible values: "left", "center", "right". Defaults to "center".
@@ -98,7 +100,8 @@ export default function BigText(element, options) {
horizontalAlign: "center",
verticalAlign: "center",
textAlign: "center",
- whiteSpace: "nowrap"
+ whiteSpace: "nowrap",
+ singleLine: true
};
//Merge provided options and default options
@@ -109,20 +112,29 @@ export default function BigText(element, options) {
//Get variables which we will reference frequently
var style = element.style;
- var computedStyle = document.defaultView.getComputedStyle(element);
var parent = element.parentNode;
var parentStyle = parent.style;
var parentComputedStyle = document.defaultView.getComputedStyle(parent);
//hides the element to prevent "flashing"
style.visibility = "hidden";
-
//Set some properties
style.display = "inline-block";
style.clear = "both";
style.float = "left";
- style.fontSize = (1000 * options.fontSizeFactor) + "px";
- style.lineHeight = "1000px";
+ var fontSize = options.maximumFontSize;
+ if (options.singleLine) {
+ style.fontSize = (fontSize * options.fontSizeFactor) + "px";
+ style.lineHeight = fontSize + "px";
+ } else {
+ for (; fontSize > options.minimumFontSize; fontSize = fontSize - Math.min(fontSize / 2, Math.max(0, fontSize - 48) + 2)) {
+ style.fontSize = (fontSize * options.fontSizeFactor) + "px";
+ style.lineHeight = "1";
+ if (element.offsetHeight <= +parentComputedStyle.height.replace("px", "")) {
+ break;
+ }
+ }
+ }
style.whiteSpace = options.whiteSpace;
style.textAlign = options.textAlign;
style.position = "relative";
@@ -130,6 +142,7 @@ export default function BigText(element, options) {
style.margin = 0;
style.left = "50%";
style.top = "50%";
+ var computedStyle = document.defaultView.getComputedStyle(element);
//Get properties of parent to allow easier referencing later.
var parentPadding = {
@@ -154,6 +167,7 @@ export default function BigText(element, options) {
width: element.offsetWidth, //Note: This is slightly larger than the jQuery version
height: element.offsetHeight,
};
+ if (!box.width || !box.height) return element;
if (options.rotateText !== null) {
@@ -172,22 +186,37 @@ export default function BigText(element, options) {
box.height = element.offsetWidth * sine + element.offsetHeight * cosine;
}
- var widthFactor = (parentInnerWidth - parentPadding.left - parentPadding.right) / box.width;
- var heightFactor = (parentInnerHeight - parentPadding.top - parentPadding.bottom) / box.height;
+ var parentWidth = (parentInnerWidth - parentPadding.left - parentPadding.right);
+ var parentHeight = (parentInnerHeight - parentPadding.top - parentPadding.bottom);
+ var widthFactor = parentWidth / box.width;
+ var heightFactor = parentHeight / box.height;
var lineHeight;
if (options.limitingDimension.toLowerCase() === "width") {
- lineHeight = Math.floor(widthFactor * 1000);
- parentStyle.height = lineHeight + "px";
+ lineHeight = Math.floor(widthFactor * fontSize);
} else if (options.limitingDimension.toLowerCase() === "height") {
- lineHeight = Math.floor(heightFactor * 1000);
+ lineHeight = Math.floor(heightFactor * fontSize);
} else if (widthFactor < heightFactor)
- lineHeight = Math.floor(widthFactor * 1000);
+ lineHeight = Math.floor(widthFactor * fontSize);
else if (widthFactor >= heightFactor)
- lineHeight = Math.floor(heightFactor * 1000);
+ lineHeight = Math.floor(heightFactor * fontSize);
var fontSize = lineHeight * options.fontSizeFactor;
- if (options.maximumFontSize !== null && fontSize > options.maximumFontSize) {
+ if (fontSize < options.minimumFontSize) {
+ parentStyle.display = "flex";
+ parentStyle.alignItems = "center";
+ style.textAlign = "center";
+ style.visibility = "";
+ style.fontSize = options.minimumFontSize + "px";
+ style.lineHeight = "";
+ style.overflow = "hidden";
+ style.textOverflow = "ellipsis";
+ style.top = "";
+ style.left = "";
+ style.margin = "";
+ return element;
+ }
+ if (options.maximumFontSize && fontSize > options.maximumFontSize) {
fontSize = options.maximumFontSize;
lineHeight = fontSize / options.fontSizeFactor;
}
@@ -197,11 +226,11 @@ export default function BigText(element, options) {
style.marginBottom = "0px";
style.marginRight = "0px";
- if (options.limitingDimension.toLowerCase() === "height") {
- //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size
- //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code
- parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px";
- }
+ // if (options.limitingDimension.toLowerCase() === "height") {
+ // //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size
+ // //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code
+ // parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px";
+ // }
//Calculate the inner width and height
var innerDimensions = _calculateInnerDimensions(computedStyle);
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 6a0d651d2..42e158584 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -4,7 +4,7 @@
border-radius: inherit;
display: flex;
flex-direction: column;
- position: absolute;
+ position: relative;
}
.labelBox-mainButton {
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 4e0461650..b58a9affb 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
-import { Cast, StrCast } from '../../../fields/Types';
+import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -23,18 +23,23 @@ export interface LabelBoxProps {
@observer
export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps)>() {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
- public static LayoutStringWithTitle(fieldType: { name: string }, fieldStr: string, label: string) {
- return `<${fieldType.name} fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ public static LayoutStringWithTitle(fieldStr: string, label?: string) {
+ return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
}
private dropDisposer?: DragManager.DragDropDisposer;
-
+ private _timeout: any;
componentDidMount() {
this.props.setContentView?.(this);
}
+ componentWillUnMount() {
+ this._timeout && clearTimeout(this._timeout);
+ }
getTitle() {
- return this.props.label ? this.props.label : this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) :
- typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
+ return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) :
+ this.props.label ? this.props.label :
+ typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) :
+ StrCast(this.rootDoc.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -47,14 +52,14 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; }
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
- funcs.push({
+ !Doc.noviceMode && funcs.push({
description: "Clear Script Params", event: () => {
const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
params?.map(p => this.paramsDoc[p] = undefined);
}, icon: "trash"
});
- ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "mouse-pointer" });
+ funcs.length && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "mouse-pointer" });
}
@undoBatch
@@ -73,8 +78,38 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
@observable _mouseOver = false;
@computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; }
+ fitTextToBox = (r: any): any => {
+ const singleLine = BoolCast(this.rootDoc._singleLine, true);
+ const params = {
+ rotateText: null,
+ fontSizeFactor: 1,
+ minimumFontSize: NumCast(this.rootDoc._minFontSize, 8),
+ maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000),
+ limitingDimension: "both",
+ horizontalAlign: "center",
+ verticalAlign: "center",
+ textAlign: "center",
+ singleLine,
+ whiteSpace: singleLine ? "nowrap" : "pre-wrap"
+ };
+ this._timeout = undefined;
+ if (!r) return params;
+ if (!r.offsetHeight || !r.offsetWidth) return this._timeout = setTimeout(() => this.fitTextToBox(r));
+ const parent = r.parentNode;
+ const parentStyle = parent.style;
+ parentStyle.display = "";
+ parentStyle.alignItems = "";
+ r.setAttribute("style", "");
+ r.style.width = singleLine ? "" : "100%";
+
+ r.style.textOverflow = "ellipsis";
+ r.style.overflow = "hidden";
+ BigText(r, params);
+ return params;
+ }
// (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
render() {
+ const boxParams = this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes
const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
@@ -87,28 +122,21 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
<div className="labelBox-mainButton" style={{
backgroundColor: this.hoverColor,
- fontSize: StrCast(this.layoutDoc._fontSize) || Math.min(18, this.props.PanelHeight() / 2),
+ fontSize: StrCast(this.layoutDoc._fontSize),
fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
letterSpacing: StrCast(this.layoutDoc.letterSpacing),
textTransform: StrCast(this.layoutDoc.textTransform) as any,
+ paddingLeft: NumCast(this.rootDoc._xPadding),
+ paddingRight: NumCast(this.rootDoc._xPadding),
+ paddingTop: NumCast(this.rootDoc._yPadding),
+ paddingBottom: NumCast(this.rootDoc._yPadding),
width: this.props.PanelWidth(),
height: this.props.PanelHeight(),
- whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap"
+ whiteSpace: boxParams.singleLine ? "pre" : "pre-wrap"
}} >
- <span ref={r => {
- if (r) {
- BigText(r, {
- rotateText: null,
- fontSizeFactor: 1,
- maximumFontSize: null,
- limitingDimension: "both",
- horizontalAlign: "center",
- verticalAlign: "center",
- textAlign: "center",
- whiteSpace: "nowrap"
- });
- }
- }}>{label.startsWith("#") ? (null) : label}</span>
+ <span style={{ width: boxParams.singleLine ? "" : "100%" }} ref={action((r: any) => this.fitTextToBox(r))}>
+ {label.startsWith("#") ? (null) : label.replace(/([^a-zA-Z])/g, "$1\u200b")}
+ </span>
</div>
<div className="labelBox-fieldKeyParams" >
{!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 437d29f39..85a8622ec 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,30 +1,31 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
-import { DragManager } from "../../util/DragManager";
-import { LinkManager } from "../../util/LinkManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { LinkEditor } from "../linking/LinkEditor";
-import { StyleProp } from "../StyleProvider";
-import { FieldView, FieldViewProps } from "./FieldView";
-import "./LinkAnchorBox.scss";
-import { LinkDocPreview } from "./LinkDocPreview";
-import React = require("react");
-const higflyout = require("@hig/flyout");
+import { DragManager } from '../../util/DragManager';
+import { LinkFollower } from '../../util/LinkFollower';
+import { SelectionManager } from '../../util/SelectionManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { LinkEditor } from '../linking/LinkEditor';
+import { StyleProp } from '../StyleProvider';
+import { FieldView, FieldViewProps } from './FieldView';
+import './LinkAnchorBox.scss';
+import { LinkDocPreview } from './LinkDocPreview';
+import React = require('react');
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-
@observer
export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(LinkAnchorBox, fieldKey);
+ }
_doubleTap = false;
_lastTap: number = 0;
_ref = React.createRef<HTMLDivElement>();
@@ -38,7 +39,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
onPointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false);
- }
+ };
onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
if (!this._isOpen && cdiv) {
@@ -47,20 +48,20 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
if (separation > 100) {
const dragData = new DragManager.DocumentDragData([this.rootDoc]);
- dragData.dropAction = "alias";
- dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isLinkButton"];
+ dragData.dropAction = 'alias';
+ dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'isLinkButton'];
DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
return true;
} else {
- this.rootDoc[this.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
- this.rootDoc[this.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
+ this.rootDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
+ this.rootDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
}
}
return false;
});
@action
onClick = (e: React.MouseEvent) => {
- if ((e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton)) {
+ if (e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton) {
this.props.select(false);
}
if (!this._doubleTap && !e.ctrlKey && e.button < 2) {
@@ -68,10 +69,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._editing = true;
anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
- this._timeout = setTimeout(action(() => {
- LinkManager.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
- this._editing = false;
- }), 300 - (Date.now() - this._lastTap));
+ this._timeout = setTimeout(
+ action(() => {
+ LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
+ this._editing = false;
+ }),
+ 300 - (Date.now() - this._lastTap)
+ );
e.stopPropagation();
}
} else {
@@ -81,18 +85,17 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.openLinkEditor(e);
e.stopPropagation();
}
- }
+ };
openLinkDocOnRight = (e: React.MouseEvent) => {
- this.props.addDocTab(this.rootDoc, "add:right");
- }
+ this.props.addDocTab(this.rootDoc, 'add:right');
+ };
openLinkTargetOnRight = (e: React.MouseEvent) => {
const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
alias._isLinkButton = undefined;
- alias._layerTags = undefined;
- alias.layoutKey = "layout";
- this.props.addDocTab(alias, "add:right");
- }
+ alias.layoutKey = 'layout';
+ this.props.addDocTab(alias, 'add:right');
+ };
@action
openLinkEditor = action((e: React.MouseEvent) => {
SelectionManager.DeselectAll();
@@ -101,56 +104,67 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Open Link Target on Right", event: () => this.openLinkTargetOnRight(e), icon: "eye" });
- funcs.push({ description: "Open Link on Right", event: () => this.openLinkDocOnRight(e), icon: "eye" });
- funcs.push({ description: "Open Link Editor", event: () => this.openLinkEditor(e), icon: "eye" });
- funcs.push({ description: "Toggle Always Show Link", event: () => this.props.Document.linkDisplay = !this.props.Document.linkDisplay, icon: "eye" });
+ funcs.push({ description: 'Open Link Target on Right', event: () => this.openLinkTargetOnRight(e), icon: 'eye' });
+ funcs.push({ description: 'Open Link on Right', event: () => this.openLinkDocOnRight(e), icon: 'eye' });
+ funcs.push({ description: 'Open Link Editor', event: () => this.openLinkEditor(e), icon: 'eye' });
+ funcs.push({ description: 'Toggle Always Show Link', event: () => (this.props.Document.linkDisplay = !this.props.Document.linkDisplay), icon: 'eye' });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
- }
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
+ };
render() {
TraceMobx();
const small = this.props.PanelWidth() <= 1; // this happens when rendered in a treeView
- const x = NumCast(this.rootDoc[this.fieldKey + "_x"], 100);
- const y = NumCast(this.rootDoc[this.fieldKey + "_y"], 100);
+ const x = NumCast(this.rootDoc[this.fieldKey + '_x'], 100);
+ const y = NumCast(this.rootDoc[this.fieldKey + '_y'], 100);
const linkSource = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
- const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ":anchor");
- const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
- const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
+ const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor');
+ const anchor = this.fieldKey === 'anchor1' ? 'anchor2' : 'anchor1';
+ const anchorScale = !this.dataDoc[this.fieldKey + '-useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
const flyout = (
<div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.rootDoc)}>
- <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => { })} />
- {!this._forceOpen ? (null) : <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
- <FontAwesomeIcon color="dimgray" icon={"times"} size={"sm"} />
- </div>}
+ <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => {})} />
+ {!this._forceOpen ? null : (
+ <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => (this._isOpen = this._editing = this._forceOpen = false))}>
+ <FontAwesomeIcon color="dimgray" icon={'times'} size={'sm'} />
+ </div>
+ )}
+ </div>
+ );
+ return (
+ <div
+ className={`linkAnchorBox-cont${small ? '-small' : ''}`}
+ onPointerLeave={LinkDocPreview.Clear}
+ onPointerEnter={e =>
+ LinkDocPreview.SetLinkInfo({
+ docProps: this.props,
+ linkSrc: linkSource,
+ linkDoc: this.rootDoc,
+ showHeader: true,
+ location: [e.clientX, e.clientY + 20],
+ })
+ }
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ title={targetTitle}
+ onContextMenu={this.specificContextMenu}
+ ref={this._ref}
+ style={{
+ background,
+ left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
+ top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
+ transform: `scale(${anchorScale})`,
+ }}>
+ {!this._editing && !this._forceOpen ? null : (
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => (this._isOpen = true)} onClose={action(() => (this._isOpen = this._forceOpen = this._editing = false))}>
+ <span className="linkAnchorBox-button">
+ <FontAwesomeIcon icon={'eye'} size={'lg'} />
+ </span>
+ </Flyout>
+ )}
</div>
);
- return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`}
- onPointerLeave={LinkDocPreview.Clear}
- onPointerEnter={e => LinkDocPreview.SetLinkInfo({
- docProps: this.props,
- linkSrc: linkSource,
- linkDoc: this.rootDoc,
- showHeader: true,
- location: [e.clientX, e.clientY + 20]
- })}
- onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
- ref={this._ref}
- style={{
- background,
- left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
- top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
- transform: `scale(${anchorScale})`
- }} >
- {!this._editing && !this._forceOpen ? (null) :
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => this._isOpen = true} onClose={action(() => this._isOpen = this._forceOpen = this._editing = false)}>
- <span className="linkAnchorBox-button" >
- <FontAwesomeIcon icon={"eye"} size={"lg"} />
- </span>
- </Flyout>}
- </div>;
}
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index a9d33f161..91bd505c5 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,26 +1,24 @@
-import React = require("react");
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { LinkManager } from "../../util/LinkManager";
-import "./LinkDescriptionPopup.scss";
-import { TaskCompletionBox } from "./TaskCompletedBox";
-
+import React = require('react');
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { LinkManager } from '../../util/LinkManager';
+import './LinkDescriptionPopup.scss';
+import { TaskCompletionBox } from './TaskCompletedBox';
@observer
export class LinkDescriptionPopup extends React.Component<{}> {
-
@observable public static descriptionPopup: boolean = false;
- @observable public static showDescriptions: string = "ON";
+ @observable public static showDescriptions: string = 'ON';
@observable public static popupX: number = 700;
@observable public static popupY: number = 350;
- @observable description: string = "";
+ @observable description: string = '';
@observable popupRef = React.createRef<HTMLDivElement>();
@action
descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
this.description = e.currentTarget.value;
- }
+ };
@action
onDismiss = (add: boolean) => {
@@ -28,7 +26,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
if (add) {
LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).description = this.description);
}
- }
+ };
@action
onClick = (e: PointerEvent) => {
@@ -36,34 +34,43 @@ export class LinkDescriptionPopup extends React.Component<{}> {
LinkDescriptionPopup.descriptionPopup = false;
TaskCompletionBox.taskCompleted = false;
}
- }
+ };
@action
componentDidMount() {
- document.addEventListener("pointerdown", this.onClick);
+ document.addEventListener('pointerdown', this.onClick, true);
}
componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onClick);
+ document.removeEventListener('pointerdown', this.onClick, true);
}
render() {
- return <div className="linkDescriptionPopup" ref={this.popupRef}
- style={{
- left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
- top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
- }}>
- <input className="linkDescriptionPopup-input"
- onKeyPress={e => e.key === "Enter" && this.onDismiss(true)}
- placeholder={"(Optional) Enter link description..."}
- onChange={(e) => this.descriptionChanged(e)}>
- </input>
- <div className="linkDescriptionPopup-btn">
- <div className="linkDescriptionPopup-btn-dismiss"
- onPointerDown={e => this.onDismiss(false)}> Dismiss </div>
- <div className="linkDescriptionPopup-btn-add"
- onPointerDown={e => this.onDismiss(true)}> Add </div>
+ return (
+ <div
+ className="linkDescriptionPopup"
+ ref={this.popupRef}
+ style={{
+ left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
+ top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+ }}>
+ <input
+ className="linkDescriptionPopup-input"
+ onKeyDown={e => e.stopPropagation()}
+ onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)}
+ placeholder={'(Optional) Enter link description...'}
+ onChange={e => this.descriptionChanged(e)}></input>
+ <div className="linkDescriptionPopup-btn">
+ <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
+ {' '}
+ Dismiss{' '}
+ </div>
+ <div className="linkDescriptionPopup-btn-add" onPointerDown={e => this.onDismiss(true)}>
+ {' '}
+ Add{' '}
+ </div>
+ </div>
</div>
- </div>;
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
index 06ae466f0..c68e55f73 100644
--- a/src/client/views/nodes/LinkDocPreview.scss
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -1,4 +1,4 @@
- .linkDocPreview {
+.linkDocPreview {
position: absolute;
pointer-events: all;
background-color: lightblue;
@@ -8,11 +8,15 @@
border-bottom: 8px solid white;
border-right: 8px solid white;
z-index: 2004;
+ cursor: pointer;
+
.linkDocPreview-inner {
background-color: white;
width: 100%;
height: 100%;
pointer-events: none;
+ display: flex;
+ flex-direction: column;
.linkDocPreview-info {
height: 37px;
@@ -21,6 +25,7 @@
.linkDocPreview-buttonBar {
float: right;
}
+
.linkDocPreview-title {
padding-right: 4px;
float: left;
@@ -59,13 +64,16 @@
cursor: pointer;
}
}
+ }
- .linkDocPreview-preview-wrapper {
- overflow: hidden;
- align-content: center;
- justify-content: center;
- background-color: rgb(160, 160, 160);
- }
+ .linkDocPreview-preview-wrapper {
+ overflow: hidden;
+ align-content: center;
+ justify-content: center;
+ pointer-events: all;
+ background-color: rgb(160, 160, 160);
+ overflow: auto;
+ cursor: grab;
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 2e29c0656..85b1f8d9e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -1,20 +1,22 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, observable } from 'mobx';
-import { observer } from "mobx-react";
-import wiki from "wikijs";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../fields/Doc";
-import { NumCast, StrCast, Cast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils";
+import { observer } from 'mobx-react';
+import wiki from 'wikijs';
+import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
import { DocServer } from '../../DocServer';
-import { Docs, DocUtils } from "../../documents/Documents";
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
+import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
-import { Transform } from "../../util/Transform";
+import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
-import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
+import { DocumentView, DocumentViewSharedProps } from './DocumentView';
import './LinkDocPreview.scss';
-import React = require("react");
-import { DocumentType } from '../../documents/DocumentTypes';
+import React = require('react');
interface LinkDocPreviewProps {
linkDoc?: Doc;
@@ -26,15 +28,20 @@ interface LinkDocPreviewProps {
}
@observer
export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
- @action public static Clear() { LinkDocPreview.LinkInfo = undefined; }
- @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); }
+ @action public static Clear() {
+ LinkDocPreview.LinkInfo = undefined;
+ }
+ @action public static SetLinkInfo(info?: LinkDocPreviewProps) {
+ LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info);
+ }
_infoRef = React.createRef<HTMLDivElement>();
+ _linkDocRef = React.createRef<HTMLDivElement>();
@observable public static LinkInfo: Opt<LinkDocPreviewProps>;
@observable _targetDoc: Opt<Doc>;
@observable _linkDoc: Opt<Doc>;
@observable _linkSrc: Opt<Doc>;
- @observable _toolTipText = "";
+ @observable _toolTipText = '';
@observable _hrefInd = 0;
@action init() {
@@ -46,94 +53,132 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
if (anchor1 && anchor2) {
linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1;
}
- if (linkTarget?.annotationOn) {
- linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno));
+ if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
+ // want to show annotation context document if annotation is not text
+ linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._targetDoc = anno)));
} else {
this._targetDoc = linkTarget;
}
- this._toolTipText = "";
+ this._toolTipText = '';
}
componentDidUpdate(props: any) {
if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
}
componentDidMount() {
this.init();
- document.addEventListener("pointerdown", this.onPointerDown);
+ document.addEventListener('pointerdown', this.onPointerDown, true);
}
componentWillUnmount() {
LinkDocPreview.SetLinkInfo(undefined);
- document.removeEventListener("pointerdown", this.onPointerDown);
+ document.removeEventListener('pointerdown', this.onPointerDown, true);
}
onPointerDown = (e: PointerEvent) => {
- !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
- }
+ !this._linkDocRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
+ };
@computed get href() {
if (this.props.hrefs?.length) {
const href = this.props.hrefs[this._hrefInd];
- if (href.indexOf(Doc.localServerPath()) !== 0) { // link to a web page URL -- try to show a preview
- if (href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+ if (href.indexOf(Doc.localServerPath()) !== 0) {
+ // link to a web page URL -- try to show a preview
+ if (href.startsWith('https://en.wikipedia.org/wiki/')) {
+ wiki()
+ .page(href.replace('https://en.wikipedia.org/wiki/', ''))
+ .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500)))));
} else {
- setTimeout(action(() => this._toolTipText = "url => " + href));
+ setTimeout(action(() => (this._toolTipText = 'url => ' + href)));
}
- } else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
- const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0];
- anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
- if (anchor instanceof Doc && DocListCast(anchor.links).length) {
- this._linkDoc = DocListCast(anchor.links)[0];
- this._linkSrc = anchor;
- const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
- this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
- this._toolTipText = "";
- }
- }));
+ } else {
+ // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
+ const anchorDoc = href.replace(Doc.localServerPath(), '').split('?')[0];
+ anchorDoc &&
+ DocServer.GetRefField(anchorDoc).then(
+ action(anchor => {
+ if (anchor instanceof Doc && DocListCast(anchor.links).length) {
+ this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
+ const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords;
+ if (automaticLink) {
+ // automatic links specify the target in the link info, not the source
+ const linkTarget = anchor;
+ this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
+ this._targetDoc = linkTarget;
+ } else {
+ this._linkSrc = anchor;
+ const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ }
+ this._toolTipText = '';
+ }
+ })
+ );
}
return href;
}
return undefined;
}
deleteLink = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc))
+ );
+ };
nextHref = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1)));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ action(() => {
+ const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ if (nextHrefInd !== this._hrefInd) {
+ this._linkDoc = undefined;
+ this._hrefInd = nextHrefInd;
+ }
+ }),
+ true
+ );
+ };
- followLink = (e: React.PointerEvent) => {
+ followLink = () => {
if (this._linkDoc && this._linkSrc) {
LinkDocPreview.Clear();
- LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
+ LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
} else if (this.props.hrefs?.length) {
- this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right");
+ this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right');
}
- }
+ };
+
+ followLinkPointerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.followLink);
+
width = () => {
if (!this._targetDoc) return 225;
if (this._targetDoc[WidthSym]() < this._targetDoc?.[HeightSym]()) {
- return Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]() / this._targetDoc[HeightSym]();
+ return (Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]()) / this._targetDoc[HeightSym]();
}
return Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
- }
+ };
height = () => {
if (!this._targetDoc) return 225;
if (this._targetDoc[WidthSym]() > this._targetDoc?.[HeightSym]()) {
- return Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]() / this._targetDoc[WidthSym]();
+ return (Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]()) / this._targetDoc[WidthSym]();
}
return Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
- }
+ };
@computed get previewHeader() {
- return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
- <div className="linkDocPreview-info" ref={this._infoRef}>
- <div className="linkDocPreview-title">
- {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
+ return !this._linkDoc || !this._targetDoc || !this._linkSrc ? null : (
+ <div className="linkDocPreview-info">
+ <div className="linkDocPreview-title" style={{ pointerEvents: 'all' }}>
+ {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + '...' : StrCast(this._targetDoc.title)}
<p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
</div>
- <div className="linkDocPreview-buttonBar" >
+ <div className="linkDocPreview-buttonBar">
<Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
- <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? "gray" : "green" }} onPointerDown={this.nextHref}>
+ <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}>
<FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
</div>
</Tooltip>
@@ -144,30 +189,54 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
</div>
</Tooltip>
</div>
- </div>;
+ </div>
+ );
}
@computed get docPreview() {
const href = this.href; // needs to be here to trigger lookup of web pages and docs on server
- return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
+ return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
<div className="linkDocPreview-inner">
- {!this.props.showHeader ? (null) : this.previewHeader}
- <div className="linkDocPreview-preview-wrapper">
- {this._toolTipText ? this._toolTipText :
- <DocumentView ref={(r) => {
- const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!);
- targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
- }}
+ {!this.props.showHeader ? null : this.previewHeader}
+ <div
+ className="linkDocPreview-preview-wrapper"
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) => {
+ if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) {
+ DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
+ LinkDocPreview.Clear();
+ return true;
+ }
+ return false;
+ },
+ emptyFunction,
+ this.followLink,
+ true
+ )
+ }
+ ref={this._infoRef}
+ style={{ maxHeight: this._toolTipText ? '100%' : undefined }}>
+ {this._toolTipText ? (
+ this._toolTipText
+ ) : (
+ <DocumentView
+ ref={r => {
+ const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
+ }}
Document={this._targetDoc!}
moveDocument={returnFalse}
rootSelected={returnFalse}
styleProvider={this.props.docProps?.styleProvider}
- layerProvider={this.props.docProps?.layerProvider}
docViewPath={returnEmptyDoclist}
ScreenToLocalTransform={Transform.Identity}
isDocumentActive={returnFalse}
- isContentActive={emptyFunction}
+ isContentActive={returnFalse}
addDocument={returnFalse}
+ showTitle={returnEmptyString}
removeDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
@@ -178,23 +247,33 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={-1}
+ suppressSetHeight={true}
PanelWidth={this.width}
PanelHeight={this.height}
+ pointerEvents={returnNone}
focus={DocUtils.DefaultFocus}
whenChildContentsActiveChanged={returnFalse}
+ ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size.
bringToFront={returnFalse}
NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
- />}
+ />
+ )}
</div>
- </div>;
+ </div>
+ );
}
render() {
const borders = 16; // 8px border on each side
- return <div className="linkDocPreview" onPointerDown={this.followLink}
- style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
- {this.docPreview}
- </div>;
+ return (
+ <div
+ className="linkDocPreview"
+ ref={this._linkDocRef}
+ onPointerDown={this.followLinkPointerDown}
+ style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+ {this.docPreview}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index 854da5ed2..fb15520f6 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -35,7 +35,7 @@
.mapBox-wrapper {
width: 100%;
- .searchbox {
+ .mapBox-input {
box-sizing: border-box;
border: 1px solid transparent;
width: 240px;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index aa2130af5..6479e933e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,18 +1,17 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api';
-import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx';
-import { observer } from "mobx-react";
-import * as React from "react";
+import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { NumCast, StrCast } from '../../../../fields/Types';
-import { TraceMobx } from '../../../../fields/util';
import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
-import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
import { DragManager } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
+import { UndoManager } from '../../../util/UndoManager';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
@@ -20,19 +19,18 @@ import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { Annotation } from '../../pdf/Annotation';
import { SidebarAnnos } from '../../SidebarAnnos';
-import { StyleProp } from '../../StyleProvider';
import { FieldView, FieldViewProps } from '../FieldView';
-import "./MapBox.scss";
+import './MapBox.scss';
import { MapBoxInfoWindow } from './MapBoxInfoWindow';
/**
* MapBox architecture:
* Main component: MapBox.tsx
* Supporting Components: SidebarAnnos, CollectionStackingView
- *
- * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
- * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
- * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
+ *
+ * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
+ * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
+ * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
* sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map).
* The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts).
* A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
@@ -77,26 +75,30 @@ document.head.appendChild(script);
// },
// });
-
// options for searchbox in Google Maps Places Autocomplete API
const options = {
- fields: ["formatted_address", "geometry", "name"], // note: level of details is charged by item per retrieval, not recommended to return all fields
+ fields: ['formatted_address', 'geometry', 'name'], // note: level of details is charged by item per retrieval, not recommended to return all fields
strictBounds: false,
- types: ["establishment"], // type pf places, subject of change according to user need
+ types: ['establishment'], // type pf places, subject of change according to user need
} as google.maps.places.AutocompleteOptions;
@observer
export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() {
-
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@observable private _overlayAnnoInfo: Opt<Doc>;
- showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBox, fieldKey); }
- public get SidebarKey() { return this.fieldKey + "-sidebar"; }
+ showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(MapBox, fieldKey);
+ }
+ public get SidebarKey() {
+ return this.fieldKey + '-sidebar';
+ }
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
- @computed get inlineTextAnnotations() { return this.allMapMarkers.filter(a => a.textInlineAnnotations); }
+ @computed get inlineTextAnnotations() {
+ return this.allMapMarkers.filter(a => a.textInlineAnnotations);
+ }
@observable private _map: google.maps.Map = null as unknown as google.maps.Map;
@observable private selectedPlace: Doc | undefined;
@@ -108,14 +110,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private searchMarkers: google.maps.Marker[] = [];
@observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options);
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); }
- @computed get allMapMarkers() { return DocListCast(this.dataDoc[this.annotationKey]); }
+ @computed get allSidebarDocs() {
+ return DocListCast(this.dataDoc[this.SidebarKey]);
+ }
+ @computed get allMapMarkers() {
+ return DocListCast(this.dataDoc[this.annotationKey]);
+ }
@observable private toggleAddMarker = false;
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-
@observable _showSidebar = false;
- @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ @computed get SidebarShown() {
+ return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
+ }
static _canAnnotate = true;
static _hadSelection: boolean = false;
@@ -129,109 +136,106 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
private setSearchBox = (searchBox: any) => {
this.searchBox = searchBox;
- }
+ };
// iterate allMarkers to size, center, and zoom map to contain all markers
private fitBounds = (map: google.maps.Map) => {
const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
- const isFitting = this.allMapMarkers.reduce((fits, place) =>
- fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
- !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) =>
- bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }),
- new window.google.maps.LatLngBounds()));
- }
+ const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
+ !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds()));
+ };
/**
* Custom control for add marker button
- * @param controlDiv
- * @param map
+ * @param controlDiv
+ * @param map
*/
private CenterControl = () => {
- const controlDiv = document.createElement("div");
- controlDiv.className = "mapBox-addMarker";
+ const controlDiv = document.createElement('div');
+ controlDiv.className = 'mapBox-addMarker';
// Set CSS for the control border.
- const controlUI = document.createElement("div");
- controlUI.style.backgroundColor = "#fff";
- controlUI.style.borderRadius = "3px";
- controlUI.style.cursor = "pointer";
- controlUI.style.marginTop = "10px";
- controlUI.style.borderRadius = "4px";
- controlUI.style.marginBottom = "22px";
- controlUI.style.textAlign = "center";
- controlUI.style.position = "absolute";
- controlUI.style.width = "32px";
- controlUI.style.height = "32px";
- controlUI.title = "Click to toggle marker mode. In marker mode, click on map to place a marker.";
-
- const plIcon = document.createElement("img");
- plIcon.src = "https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png";
- plIcon.style.color = "rgb(25,25,25)";
- plIcon.style.fontFamily = "Roboto,Arial,sans-serif";
- plIcon.style.fontSize = "16px";
- plIcon.style.lineHeight = "32px";
- plIcon.style.left = "18";
- plIcon.style.top = "15";
- plIcon.style.position = "absolute";
+ const controlUI = document.createElement('div');
+ controlUI.style.backgroundColor = '#fff';
+ controlUI.style.borderRadius = '3px';
+ controlUI.style.cursor = 'pointer';
+ controlUI.style.marginTop = '10px';
+ controlUI.style.borderRadius = '4px';
+ controlUI.style.marginBottom = '22px';
+ controlUI.style.textAlign = 'center';
+ controlUI.style.position = 'absolute';
+ controlUI.style.width = '32px';
+ controlUI.style.height = '32px';
+ controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.';
+
+ const plIcon = document.createElement('img');
+ plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png';
+ plIcon.style.color = 'rgb(25,25,25)';
+ plIcon.style.fontFamily = 'Roboto,Arial,sans-serif';
+ plIcon.style.fontSize = '16px';
+ plIcon.style.lineHeight = '32px';
+ plIcon.style.left = '18';
+ plIcon.style.top = '15';
+ plIcon.style.position = 'absolute';
plIcon.width = 14;
plIcon.height = 14;
- plIcon.innerHTML = "Add";
+ plIcon.innerHTML = 'Add';
controlUI.appendChild(plIcon);
// Set CSS for the control interior.
- const markerIcon = document.createElement("img");
- markerIcon.src = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png";
- markerIcon.style.color = "rgb(25,25,25)";
- markerIcon.style.fontFamily = "Roboto,Arial,sans-serif";
- markerIcon.style.fontSize = "16px";
- markerIcon.style.lineHeight = "32px";
- markerIcon.style.left = "-2";
- markerIcon.style.top = "1";
+ const markerIcon = document.createElement('img');
+ markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png';
+ markerIcon.style.color = 'rgb(25,25,25)';
+ markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif';
+ markerIcon.style.fontSize = '16px';
+ markerIcon.style.lineHeight = '32px';
+ markerIcon.style.left = '-2';
+ markerIcon.style.top = '1';
markerIcon.width = 30;
markerIcon.height = 30;
- markerIcon.style.position = "absolute";
- markerIcon.innerHTML = "Add";
+ markerIcon.style.position = 'absolute';
+ markerIcon.innerHTML = 'Add';
controlUI.appendChild(markerIcon);
// Setup the click event listeners
- controlUI.addEventListener("click", () => {
+ controlUI.addEventListener('click', () => {
if (this.toggleAddMarker === true) {
this.toggleAddMarker = false;
- console.log("add marker button status:" + this.toggleAddMarker);
- controlUI.style.backgroundColor = "#fff";
- markerIcon.style.color = "rgb(25,25,25)";
+ console.log('add marker button status:' + this.toggleAddMarker);
+ controlUI.style.backgroundColor = '#fff';
+ markerIcon.style.color = 'rgb(25,25,25)';
} else {
this.toggleAddMarker = true;
- console.log("add marker button status:" + this.toggleAddMarker);
- controlUI.style.backgroundColor = "#4476f7";
- markerIcon.style.color = "rgb(255,255,255)";
+ console.log('add marker button status:' + this.toggleAddMarker);
+ controlUI.style.backgroundColor = '#4476f7';
+ markerIcon.style.color = 'rgb(255,255,255)';
}
});
controlDiv.appendChild(controlUI);
return controlDiv;
- }
+ };
/**
* Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list
* @param position - the LatLng position where the marker is placed
- * @param map
+ * @param map
*/
@action
private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => {
const marker = new google.maps.Marker({
position: position,
- map: map
+ map: map,
});
map.panTo(position);
const mapMarker = Docs.Create.MapMarkerDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {});
this.addDocument(mapMarker, this.annotationKey);
- }
+ };
_loadPending = true;
/**
* store a reference to google map instance
* setup the drawing manager on the top right corner of map
- * fit map bounds to contain all markers
- * @param map
+ * fit map bounds to contain all markers
+ * @param map
*/
@action
private loadHandler = (map: google.maps.Map) => {
@@ -256,10 +260,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
map.setZoom(NumCast(this.dataDoc.mapZoom, 2.5));
map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
setTimeout(() => {
-
if (this._loadPending && this._map.getBounds()) {
this._loadPending = false;
- this.layoutDoc.fitToBox && this.fitBounds(this._map);
+ this.layoutDoc.fitContentsToBox && this.fitBounds(this._map);
}
}, 250);
// listener to addmarker event
@@ -268,83 +271,83 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.placeMarker((e as any).latLng, map);
}
});
- }
+ };
@action
centered = () => {
if (this._loadPending && this._map.getBounds()) {
this._loadPending = false;
- this.layoutDoc.fitToBox && this.fitBounds(this._map);
+ this.layoutDoc.fitContentsToBox && this.fitBounds(this._map);
}
this.dataDoc.mapLat = this._map.getCenter()?.lat();
this.dataDoc.mapLng = this._map.getCenter()?.lng();
- }
+ };
@action
zoomChanged = () => {
if (this._loadPending && this._map.getBounds()) {
this._loadPending = false;
- this.layoutDoc.fitToBox && this.fitBounds(this._map);
+ this.layoutDoc.fitContentsToBox && this.fitBounds(this._map);
}
this.dataDoc.mapZoom = this._map.getZoom();
- }
+ };
/**
* Load and render all map markers
- * @param marker
- * @param place
+ * @param marker
+ * @param place
*/
@action
private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => {
- place[Id] ? this.markerMap[place[Id]] = marker : null;
- }
+ place[Id] ? (this.markerMap[place[Id]] = marker) : null;
+ };
/**
* on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true
- * @param e
- * @param place
+ * @param e
+ * @param place
*/
@action
private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => {
// set which place was clicked
this.selectedPlace = place;
place.infoWindowOpen = true;
- }
+ };
/**
* Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
- * @param doc
- * @param sidebarKey
- * @returns
+ * @param doc
+ * @param sidebarKey
+ * @returns
*/
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
- console.log("print all sidebar Docs");
+ console.log('print all sidebar Docs');
console.log(this.allSidebarDocs);
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
const docs = doc instanceof Doc ? [doc] : doc;
docs.forEach(doc => {
if (doc.lat !== undefined && doc.lng !== undefined) {
const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng);
- doc.onClickBehavior = "enterPortal";
+ doc.onClickBehavior = 'enterPortal';
if (existingMarker) {
- Doc.AddDocToList(existingMarker, "data", doc);
+ Doc.AddDocToList(existingMarker, 'data', doc);
} else {
const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {});
this.addDocument(marker, this.annotationKey);
}
}
}); //add to annotation list
- console.log("sidebaraddDocument");
+ console.log('sidebaraddDocument');
console.log(doc);
return this.addDocument(doc, sidebarKey); // add to sidebar list
- }
+ };
/**
* Removing documents from the sidebar
- * @param doc
- * @param sidebarKey
- * @returns
+ * @param doc
+ * @param sidebarKey
+ * @returns
*/
sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (this.layoutDoc._showSidebar) this.toggleSidebar();
@@ -354,31 +357,47 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
console.log(this.allSidebarDocs);
});
return this.removeDocument(doc, sidebarKey);
- }
+ };
/**
* Toggle sidebar onclick the tiny comment button on the top right corner
- * @param e
+ * @param e
*/
sidebarBtnDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, (e, down, delta) => {
- const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]);
- const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
- const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
- if (ratio >= 1) {
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0];
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
- }
- return false;
- }, emptyFunction, this.toggleSidebar);
- }
-
- sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
- @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
- @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) =>
+ runInAction(() => {
+ const localDelta = this.props
+ .ScreenToLocalTransform()
+ .scale(this.props.NativeDimScaling?.() || 1)
+ .transformDirection(delta[0], delta[1]);
+ const fullWidth = this.layoutDoc[WidthSym]();
+ const mapWidth = fullWidth - this.sidebarWidth();
+ if (this.sidebarWidth() + localDelta[0] > 0) {
+ this._showSidebar = true;
+ this.layoutDoc._width = fullWidth + localDelta[0];
+ this.layoutDoc._sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+ } else {
+ this._showSidebar = false;
+ this.layoutDoc._width = mapWidth;
+ this.layoutDoc._sidebarWidthPercent = '0%';
+ }
+ return false;
+ }),
+ emptyFunction,
+ () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
+ );
+ };
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ @computed get sidebarWidthPercent() {
+ return StrCast(this.layoutDoc._sidebarWidthPercent, '0%');
+ }
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4'));
+ }
/**
* function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted;
@@ -414,7 +433,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
// put temporary cutomized marker on searched location
- this.searchMarkers.forEach((marker) => {
+ this.searchMarkers.forEach(marker => {
marker.setMap(null);
});
this.searchMarkers = [];
@@ -426,100 +445,106 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
position: place.geometry.location,
})
);
- }
+ };
/**
* Handles toggle of sidebar on click the little comment button
*/
@computed get sidebarHandle() {
- TraceMobx();
- const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
- const color = !annotated ? Colors.WHITE : Colors.BLACK;
- const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
- return (!annotated) ? (null) :
- <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+ return (
+ <div
+ className="mapBox-overlayButton-sidebar"
+ key="sidebar"
+ title="Toggle Sidebar"
style={{
- left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
- backgroundColor: backgroundColor,
- color: color,
- opacity: annotated ? 1 : undefined
- }} >
- <FontAwesomeIcon icon={"comment-alt"} />
- </div>;
+ display: !this.props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+ }}
+ onPointerDown={this.sidebarBtnDown}>
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ </div>
+ );
}
// TODO: Adding highlight box layer to Maps
@action
toggleSidebar = () => {
+ //1.2 * w * ? = .2 * w .2/1.2
const prevWidth = this.sidebarWidth();
- this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
- this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
- }
+ this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+ this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+ };
sidebarDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
- }
+ setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
+ };
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const bounds = this._ref.current!.getBoundingClientRect();
- this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
- this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+ this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
+ this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%';
e.preventDefault();
return false;
- }
+ };
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
@action
onMarqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
- setupMoveUpEvents(this, e, action(e => {
- MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeing = [e.clientX, e.clientY];
- return true;
- }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }),
+ returnFalse,
+ () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
+ false
+ );
}
- }
+ };
@action finishMarquee = (x?: number, y?: number) => {
this._marqueeing = undefined;
this._isAnnotating = false;
x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false);
- }
+ };
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
return this.addDocument(doc, annotationKey);
- }
+ };
+ pointerEvents = () => {
+ return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
+ };
@computed get annotationLayer() {
- const pe = this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" :
- SnappingManager.GetIsDragging() ? undefined : "none";
- return <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
- {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation key={`${anno[Id]}-annotation`} {...this.props}
- fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />)}
- </div>;
+ return (
+ <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
+ {this.inlineTextAnnotations
+ .sort((a, b) => NumCast(a.y) - NumCast(b.y))
+ .map(anno => (
+ <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />
+ ))}
+ </div>
+ );
}
getAnchor = () => {
- const anchor =
- AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
- this.rootDoc;
+ const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? this.rootDoc;
return anchor;
- }
+ };
/**
* render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker
- * @returns
+ * @returns
*/
private renderMarkers = () => {
return this.allMapMarkers.map(place => (
- <Marker
- key={place[Id]}
- position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }}
- onLoad={marker => this.markerLoadHandler(marker, place)}
- onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)}
- />
+ <Marker key={place[Id]} position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} onLoad={marker => this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} />
));
- }
+ };
// TODO: auto center on select a document in the sidebar
private handleMapCenter = (map: google.maps.Map) => {
@@ -527,19 +552,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// if (SelectionManager.Views().lastElement()) {
// console.log(SelectionManager.Views().lastElement());
// }
- }
+ };
- panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
- panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
infoWidth = () => this.props.PanelWidth() / 5;
infoHeight = () => this.props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-
+ savedAnnotations = () => this._savedAnnotations;
render() {
- const renderAnnotations = (docFilters?: () => string[]) => (null);
+ const renderAnnotations = (docFilters?: () => string[]) => null;
// bcz: commmented this out. Otherwise, any documents that are rendered with an InfoWindow of a marker
// will also be rendered as freeform annotations on the map. However, it doesn't seem that rendering
// freeform documents on the map does anything anyway, so getting rid of it for now. Also, since documents
@@ -559,96 +584,84 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// docFilters={docFilters || this.props.docFilters}
// dontRenderDocuments={docFilters ? false : true}
// select={emptyFunction}
- // ContentScaling={returnOne}
// bringToFront={emptyFunction}
// whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
// removeDocument={this.removeDocument}
// moveDocument={this.moveDocument}
// addDocument={this.sidebarAddDocument}
- // childPointerEvents={true}
- // pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
- return <div className="mapBox" ref={this._ref}>
- {/*console.log(apiKey)*/}
- {/* <LoadScript
+ // childPointerEvents={"all"}
+ // pointerEvents={Doc.ActiveTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
+ return (
+ <div className="mapBox" ref={this._ref}>
+ {/*console.log(apiKey)*/}
+ {/* <LoadScript
googleMapsApiKey={apiKey!}
libraries={['places', 'drawing']}
> */}
- <div className="mapBox-wrapper"
- onWheel={e => e.stopPropagation()}
- onPointerDown={e => (e.button === 0 && !e.ctrlKey) && e.stopPropagation()}
- style={{ width: `calc(100% - ${this.sidebarWidthPercent})` }}>
-
- <div style={{ mixBlendMode: "multiply" }}>
- {renderAnnotations(this.transparentFilter)}
+ <div className="mapBox-wrapper" onWheel={e => e.stopPropagation()} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} style={{ width: `calc(100% - ${this.sidebarWidthPercent})` }}>
+ <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
+ {renderAnnotations(this.opaqueFilter)}
+ {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
+ {this.annotationLayer}
+ <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}>
+ <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}>
+ <input className="mapBox-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" />
+ </Autocomplete>
+
+ {this.renderMarkers()}
+ {this.allMapMarkers
+ .filter(marker => marker.infoWindowOpen)
+ .map(marker => (
+ <MapBoxInfoWindow
+ key={marker[Id]}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ place={marker}
+ markerMap={this.markerMap}
+ PanelWidth={this.infoWidth}
+ PanelHeight={this.infoHeight}
+ moveDocument={this.moveDocument}
+ isAnyChildContentActive={this.isAnyChildContentActive}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ />
+ ))}
+ {/* {this.handleMapCenter(this._map)} */}
+ </GoogleMap>
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ <MarqueeAnnotator
+ rootDoc={this.rootDoc}
+ anchorMenuClick={this.anchorMenuClick}
+ scrollTop={0}
+ down={this._marqueeing}
+ scaling={returnOne}
+ addDocument={this.addDocumentWrapper}
+ docView={this.props.docViewPath().lastElement()}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ />
+ )}
</div>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
- {this.annotationLayer}
- <GoogleMap
- mapContainerStyle={mapContainerStyle}
- onZoomChanged={this.zoomChanged}
- onCenterChanged={this.centered}
- onLoad={this.loadHandler}
- options={mapOptions}
- >
- <Autocomplete
- onLoad={this.setSearchBox}
- onPlaceChanged={this.handlePlaceChanged}>
- <input ref={this.inputRef} className="searchbox" type="text" placeholder="Search anywhere:" />
- </Autocomplete>
-
- {this.renderMarkers()}
- {this.allMapMarkers.filter(marker => marker.infoWindowOpen).map(marker => <MapBoxInfoWindow key={marker[Id]}
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- place={marker}
- markerMap={this.markerMap}
- PanelWidth={this.infoWidth}
- PanelHeight={this.infoHeight}
- moveDocument={this.moveDocument}
- isAnyChildContentActive={this.isAnyChildContentActive}
+ {/* </LoadScript > */}
+ <div className="mapBox-sidebar" style={{ position: 'absolute', right: 0, height: '100%', width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ <SidebarAnnos
+ ref={this._sidebarRef}
+ {...this.props}
+ fieldKey={this.fieldKey}
+ rootDoc={this.rootDoc}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ showSidebar={this.SidebarShown}
+ nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- />)}
- {this.handleMapCenter(this._map)}
- </GoogleMap>
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc}
- anchorMenuClick={this.anchorMenuClick}
- scrollTop={0}
- down={this._marqueeing} scaling={returnOne}
- addDocument={this.addDocumentWrapper}
- docView={this.props.docViewPath().lastElement()}
- finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
- annotationLayer={this._annotationLayer.current}
- mainCont={this._mainCont.current} />}
- </div>
- {/* </LoadScript > */}
- <div className="mapBox-sidebar"
- style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- <SidebarAnnos ref={this._sidebarRef}
- {...this.props}
- fieldKey={this.fieldKey}
- rootDoc={this.rootDoc}
- layoutDoc={this.layoutDoc}
- dataDoc={this.dataDoc}
- showSidebar={this.SidebarShown}
- nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- PanelWidth={this.sidebarWidth}
- sidebarAddDocument={this.sidebarAddDocument}
- moveDocument={this.moveDocument}
- removeDocument={this.sidebarRemoveDocument}
- />
- </div>
- <div className="mapBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
- style={{
- display: !this.props.isContentActive() ? "none" : undefined,
- top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5,
- backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK
- }}
- onPointerDown={this.sidebarBtnDown} >
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+ PanelWidth={this.sidebarWidth}
+ sidebarAddDocument={this.sidebarAddDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.sidebarRemoveDocument}
+ />
+ </div>
+ {this.sidebarHandle}
</div>
- </div>;
+ );
}
}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 5e5f6cd74..00bedafbe 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -1,20 +1,18 @@
import { InfoWindow } from '@react-google-maps/api';
-import { action, computed } from 'mobx';
-import { observer } from "mobx-react";
-import * as React from "react";
+import { action } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
-import { emptyFunction, OmitKeys, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, OmitKeys, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
-import { DocumentType } from '../../../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
-import { CollectionViewType } from '../../collections/CollectionView';
import { ViewBoxAnnotatableProps } from '../../DocComponent';
import { FieldViewProps } from '../FieldView';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import "./MapBox.scss";
-
+import './MapBox.scss';
interface MapBoxInfoWindowProps {
place: Doc;
@@ -23,71 +21,74 @@ interface MapBoxInfoWindowProps {
isAnyChildContentActive: () => boolean;
}
@observer
-export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps>{
-
+export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> {
@action
private handleInfoWindowClose = () => {
if (this.props.place.infoWindowOpen) {
this.props.place.infoWindowOpen = false;
}
this.props.place.infoWindowOpen = false;
- }
+ };
addNoteClick = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => {
- const newBox = Docs.Create.TextDocument("Note", { _autoHeight: true });
- FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
- Doc.AddDocToList(this.props.place, "data", newBox);
+ const newBox = Docs.Create.TextDocument('Note', { _autoHeight: true });
+ FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ Doc.AddDocToList(this.props.place, 'data', newBox);
this._stack?.scrollToBottom();
e.stopPropagation();
e.preventDefault();
});
- }
-
+ };
_stack: CollectionStackingView | CollectionNoteTakingView | null | undefined;
-
- // Collection stacking view for documents in the infowindow of a map marker
- @computed get renderChildDocs() {
- return;
- }
+ childFitWidth = (doc: Doc) => doc.type === DocumentType.RTF;
+ addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean);
+ removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean);
render() {
- return <InfoWindow anchor={this.props.markerMap[this.props.place[Id]]} onCloseClick={this.handleInfoWindowClose} >
- <div className="mapbox-infowindow">
- <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
- <CollectionStackingView
- ref={r => this._stack = r}
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- Document={this.props.place}
- DataDoc={undefined}
- fieldKey="data"
- CollectionView={undefined}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- docFilters={returnEmptyFilter}
- setHeight={emptyFunction}
- isAnnotationOverlay={false}
- select={emptyFunction}
- scaling={returnOne}
- isContentActive={returnTrue}
- chromeHidden={true}
- rootSelected={returnFalse}
- childHideResizeHandles={returnTrue}
- childHideDecorationTitle={returnTrue}
- childFitWidth={doc => doc.type === DocumentType.RTF}
- // childDocumentsActive={returnFalse}
- removeDocument={(doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, "data", d), true as boolean)}
- addDocument={(doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, "data", d), true as boolean)}
- renderDepth={this.props.renderDepth + 1}
- viewType={CollectionViewType.Stacking}
- pointerEvents="all"
- />
+ return (
+ <InfoWindow anchor={this.props.markerMap[this.props.place[Id]]} onCloseClick={this.handleInfoWindowClose}>
+ <div className="mapbox-infowindow">
+ <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
+ <CollectionStackingView
+ ref={r => (this._stack = r)}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ Document={this.props.place}
+ DataDoc={undefined}
+ fieldKey="data"
+ CollectionView={undefined}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ docFilters={returnEmptyFilter}
+ setHeight={emptyFunction}
+ isAnnotationOverlay={false}
+ select={emptyFunction}
+ NativeDimScaling={returnOne}
+ isContentActive={returnTrue}
+ chromeHidden={true}
+ rootSelected={returnFalse}
+ childHideResizeHandles={returnTrue}
+ childHideDecorationTitle={returnTrue}
+ childFitWidth={this.childFitWidth}
+ // childDocumentsActive={returnFalse}
+ removeDocument={this.removeDoc}
+ addDocument={this.addDoc}
+ renderDepth={this.props.renderDepth + 1}
+ viewType={CollectionViewType.Stacking}
+ pointerEvents={returnAll}
+ />
+ </div>
+ <hr />
+ <div
+ onPointerDown={this.addNoteClick}
+ onClick={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}>
+ Add Note
+ </div>
</div>
- <hr />
- <div onPointerDown={this.addNoteClick} onClick={e => { e.stopPropagation(); e.preventDefault(); }} >
- Add Note
- </div>
- </div>
- </InfoWindow>;
+ </InfoWindow>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 9807cee7c..345407c2f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,33 +1,40 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
-import { observer } from "mobx-react";
-import * as Pdfjs from "pdfjs-dist";
-import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
-import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types';
-import { PdfField } from "../../../fields/URLField";
+import { observer } from 'mobx-react';
+import * as Pdfjs from 'pdfjs-dist';
+import 'pdfjs-dist/web/pdf_viewer.css';
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
import { KeyCodes } from '../../util/KeyCodes';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { Colors } from '../global/globalEnums';
-import { AnchorMenu } from '../pdf/AnchorMenu';
-import { PDFViewer } from "../pdf/PDFViewer";
+import { CreateImage } from '../nodes/WebBoxRenderer';
+import { PDFViewer } from '../pdf/PDFViewer';
import { SidebarAnnos } from '../SidebarAnnos';
import { FieldView, FieldViewProps } from './FieldView';
-import "./PDFBox.scss";
-import React = require("react");
+import { ImageBox } from './ImageBox';
+import './PDFBox.scss';
+import { VideoBox } from './VideoBox';
+import React = require('react');
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(PDFBox, fieldKey);
+ }
public static openSidebarWidth = 250;
public static sidebarResizerWidth = 5;
- private _searchString: string = "";
+ private _searchString: string = '';
private _initialScrollTarget: Opt<Doc>;
private _pdfViewer: PDFViewer | undefined;
private _searchRef = React.createRef<HTMLInputElement>();
@@ -38,8 +45,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
@observable private _pageControls = false;
- @computed get pdfUrl() { return Cast(this.dataDoc[this.props.fieldKey], PdfField); }
- @computed get pdfThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; }
+ @computed get pdfUrl() {
+ return Cast(this.dataDoc[this.props.fieldKey], PdfField);
+ }
+ @computed get pdfThumb() {
+ return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
+ }
constructor(props: any) {
super(props);
@@ -47,49 +58,173 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
!this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
if (this.pdfUrl) {
- if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href));
- else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => this._pdf = pdf));
+ if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
+ else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
}
}
- componentWillUnmount() { this._selectReactionDisposer?.(); }
+ replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
+ if (oldDiv.childNodes) {
+ for (let i = 0; i < oldDiv.childNodes.length; i++) {
+ this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
+ }
+ }
+
+ if (oldDiv.className === 'pdfBox-ui' || oldDiv.className === 'pdfViewerDash-overlay-inking') {
+ newDiv.style.display = 'none';
+ }
+ if (newDiv && newDiv.style) newDiv.style.overflow = 'hidden';
+ if (oldDiv instanceof HTMLCanvasElement) {
+ const canvas = oldDiv;
+ const img = document.createElement('img'); // create a Image Element
+ img.src = canvas.toDataURL(); //image sourcez
+ img.style.width = canvas.style.width;
+ img.style.height = canvas.style.height;
+ const newCan = newDiv as HTMLCanvasElement;
+ const parEle = newCan.parentElement as HTMLElement;
+ parEle.removeChild(newCan);
+ parEle.appendChild(img);
+ }
+ };
+
+ crop = (region: Doc | undefined, addCrop?: boolean) => {
+ if (!region) return;
+ const cropping = Doc.MakeCopy(region, true);
+ Doc.GetProto(region).lockedPosition = true;
+ Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
+ Doc.GetProto(region).isPushpin = true;
+ this.addDocument(region);
+
+ const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
+ newDiv.style.width = this.layoutDoc[WidthSym]().toString();
+ newDiv.style.height = this.layoutDoc[HeightSym]().toString();
+ this.replaceCanvases(docViewContent, newDiv);
+ const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
+
+ const anchx = NumCast(cropping.x);
+ const anchy = NumCast(cropping.y);
+ const anchw = cropping[WidthSym]() * (this.props.NativeDimScaling?.() || 1);
+ const anchh = cropping[HeightSym]() * (this.props.NativeDimScaling?.() || 1);
+ const viewScale = 1;
+ cropping.title = 'crop: ' + this.rootDoc.title;
+ cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
+ cropping.y = NumCast(this.rootDoc.y);
+ cropping._width = anchw;
+ cropping._height = anchh;
+ cropping.isLinkButton = undefined;
+ const croppingProto = Doc.GetProto(cropping);
+ croppingProto.annotationOn = undefined;
+ croppingProto.isPrototype = true;
+ croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ croppingProto.type = DocumentType.IMG;
+ croppingProto.layout = ImageBox.LayoutString('data');
+ croppingProto.data = new ImageField(Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
+ croppingProto['data-nativeWidth'] = anchw;
+ croppingProto['data-nativeHeight'] = anchh;
+ if (addCrop) {
+ DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+ }
+ this.props.bringToFront(cropping);
+
+ CreateImage(
+ '',
+ document.styleSheets,
+ htmlString,
+ anchw,
+ anchh,
+ (NumCast(region.y) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '-nativeWidth']),
+ (NumCast(region.x) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '-nativeWidth']),
+ 4
+ )
+ .then((data_url: any) => {
+ VideoBox.convertDataUri(data_url, region[Id]).then(returnedfilename =>
+ setTimeout(
+ action(() => {
+ croppingProto.data = new ImageField(returnedfilename);
+ }),
+ 500
+ )
+ );
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+
+ return cropping;
+ };
+
+ updateIcon = () => {
+ // currently we render pdf icons as text labels
+ const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
+ this._pdfViewer?._mainCont.current &&
+ CollectionFreeFormView.UpdateIcon(
+ filename,
+ docViewContent,
+ this.layoutDoc[WidthSym](),
+ this.layoutDoc[HeightSym](),
+ this.props.PanelWidth(),
+ this.props.PanelHeight(),
+ NumCast(this.layoutDoc._scrollTop),
+ NumCast(this.rootDoc[this.fieldKey + '-nativeHeight'], 1),
+ true,
+ this.layoutDoc[Id] + '-icon',
+ (iconFile: string, nativeWidth: number, nativeHeight: number) => {
+ setTimeout(() => {
+ this.dataDoc.icon = new ImageField(iconFile);
+ this.dataDoc['icon-nativeWidth'] = nativeWidth;
+ this.dataDoc['icon-nativeHeight'] = nativeHeight;
+ }, 500);
+ }
+ );
+ };
+
+ componentWillUnmount() {
+ this._selectReactionDisposer?.();
+ }
componentDidMount() {
this.props.setContentView?.(this);
- this._selectReactionDisposer = reaction(() => this.props.isSelected(),
+ this._selectReactionDisposer = reaction(
+ () => this.props.isSelected(),
() => {
- document.removeEventListener("keydown", this.onKeyDown);
- this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown);
- }, { fireImmediately: true });
+ document.removeEventListener('keydown', this.onKeyDown);
+ this.props.isSelected(true) && document.addEventListener('keydown', this.onKeyDown);
+ },
+ { fireImmediately: true }
+ );
}
scrollFocus = (doc: Doc, smooth: boolean) => {
- if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+ let didToggle = false;
+ if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
this.toggleSidebar(!smooth);
+ didToggle = true;
}
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
this._initialScrollTarget = doc;
- return this._pdfViewer?.scrollFocus(doc, smooth);
- }
+ return this._pdfViewer?.scrollFocus(doc, smooth) ?? (didToggle ? 1 : undefined);
+ };
getAnchor = () => {
const anchor =
- AnchorMenu.Instance?.GetAnchor() ??
+ this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ??
Docs.Create.TextanchorDocument({
- title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)),
+ title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)),
y: NumCast(this.layoutDoc._scrollTop),
- unrendered: true
+ unrendered: true,
});
this.addDocument(anchor);
return anchor;
- }
+ };
@action
loaded = (nw: number, nh: number, np: number) => {
- this.dataDoc[this.props.fieldKey + "-numPages"] = np;
- Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), nw * 96 / 72));
- Doc.SetNativeHeight(this.dataDoc, nh * 96 / 72);
+ this.dataDoc[this.props.fieldKey + '-numPages'] = np;
+ Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
+ Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
this.layoutDoc._height = this.layoutDoc[WidthSym]() / (Doc.NativeAspect(this.dataDoc) || 1);
!this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
- }
+ };
public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
if (!this._searching && !clear) {
@@ -104,16 +239,26 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
});
public prevAnnotation = () => this._pdfViewer?.prevAnnotation();
public nextAnnotation = () => this._pdfViewer?.nextAnnotation();
- public backPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) - 1; return true; };
- public forwardPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) + 1; return true; };
- public gotoPage = (p: number) => this.Document._curPage = p;
+ public backPage = () => {
+ this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1);
+ return true;
+ };
+ public forwardPage = () => {
+ this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + '-numPages']), (NumCast(this.Document._curPage) || 1) + 1);
+ return true;
+ };
+ public gotoPage = (p: number) => (this.Document._curPage = p);
@undoBatch
onKeyDown = action((e: KeyboardEvent) => {
let processed = false;
switch (e.key) {
- case "PageDown": processed = this.forwardPage(); break;
- case "PageUp": processed = this.backPage(); break;
+ case 'PageDown':
+ processed = this.forwardPage();
+ break;
+ case 'PageUp':
+ processed = this.backPage();
+ break;
}
if (processed) {
e.stopImmediatePropagation();
@@ -127,180 +272,232 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.scrollFocus(this._initialScrollTarget, false);
this._initialScrollTarget = undefined;
}
- }
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
+ };
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
// adding external documents; to sidebar key
// if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"-annotation")
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
- }
- sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
- setupMoveUpEvents(this, e, (e, down, delta) => {
- const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]);
- const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
- const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
- if (ratio >= 1) {
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ };
+ sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => {
+ // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
+ const batch = UndoManager.StartBatch('sidebar');
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) => {
+ const localDelta = this.props
+ .ScreenToLocalTransform()
+ .scale(this.props.NativeDimScaling?.() || 1)
+ .transformDirection(delta[0], delta[1]);
+ const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
+ const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
+ const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
+ if (ratio >= 1) {
+ this.layoutDoc.nativeWidth = nativeWidth * ratio;
+ onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
+ this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ }
+ return false;
+ },
+ (e, movement, isClick) => !isClick && batch.end(),
+ () => {
+ this.toggleSidebar();
+ batch.end();
}
- return false;
- }, emptyFunction, () => this.toggleSidebar());
- }
+ );
+ };
@observable _previewNativeWidth: Opt<number> = undefined;
@observable _previewWidth: Opt<number> = undefined;
toggleSidebar = action((preview: boolean = false) => {
- const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
+ const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth;
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
if (preview) {
this._previewNativeWidth = nativeWidth * sideratio;
- this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * sideratio / curNativeWidth;
+ this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth;
this._showSidebar = true;
- }
- else {
+ } else {
this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * pdfratio / curNativeWidth;
+ this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth;
this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
}
});
settingsPanel() {
- const pageBtns = <>
- <button className="pdfBox-backBtn" key="back" title="Page Back"
- onPointerDown={e => e.stopPropagation()} onClick={this.backPage} >
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-left"} size="sm" />
- </button>
- <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward"
- onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage} >
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-right"} size="sm" />
- </button>
- </>;
- const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
+ const pageBtns = (
+ <>
+ <button className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
+ <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-left'} size="sm" />
+ </button>
+ <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
+ <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-right'} size="sm" />
+ </button>
+ </>
+ );
+ const searchTitle = `${!this._searching ? 'Open' : 'Close'} Search Bar`;
const curPage = NumCast(this.Document._curPage) || 1;
- return !this.props.isContentActive() ? (null) :
- <div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true}
- onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
- <div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
+ return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
+ <div
+ className="pdfBox-ui"
+ onKeyDown={e => ([KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true)}
+ onPointerDown={e => e.stopPropagation()}
+ style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
+ <div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title={searchTitle} />
- <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
- onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
+ <input
+ className="pdfBox-searchBar"
+ placeholder="Search"
+ ref={this._searchRef}
+ onChange={this.searchStringChanged}
+ onKeyDown={e => {
+ e.stopPropagation();
+ e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
+ }}
+ />
<button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
- <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation} >
- <FontAwesomeIcon icon={"arrow-up"} size="lg" />
+ <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
+ <FontAwesomeIcon icon={'arrow-up'} size="lg" />
</button>
- <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation} >
- <FontAwesomeIcon icon={"arrow-down"} size="lg" />
+ <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
+ <FontAwesomeIcon icon={'arrow-down'} size="lg" />
</button>
</div>
- <button className="pdfBox-overlayButton" title={searchTitle}
- onClick={action(() => { this._searching = !this._searching; this.search("", true, true); })} >
- <div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()} />
- <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon icon={this._searching ? "times" : "search"} size="lg" />
+ <button
+ className="pdfBox-overlayButton"
+ title={searchTitle}
+ onClick={action(() => {
+ this._searching = !this._searching;
+ this.search('', true, true);
+ })}>
+ <div className="pdfBox-overlayButton-arrow" onPointerDown={e => e.stopPropagation()} />
+ <div className="pdfBox-overlayButton-iconCont" onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon icon={this._searching ? 'times' : 'search'} size="lg" />
</div>
</button>
<div className="pdfBox-pageNums">
- <input value={curPage} style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }}
- onChange={e => this.Document._curPage = Number(e.currentTarget.value)}
- onClick={action(() => this._pageControls = !this._pageControls)} />
- {this._pageControls ? pageBtns : (null)}
- </div>
- <div className="pdfBox-sidebarBtn" key="sidebar" title="Toggle Sidebar"
- style={{
- display: !this.props.isContentActive() ? "none" : undefined,
- top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5,
- backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK
- }}
- onPointerDown={e => this.sidebarBtnDown(e, true)} >
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+ <input
+ value={curPage}
+ style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: 'all' }}
+ onChange={e => (this.Document._curPage = Number(e.currentTarget.value))}
+ onKeyDown={e => e.stopPropagation()}
+ onClick={action(() => (this._pageControls = !this._pageControls))}
+ />
+ {this._pageControls ? pageBtns : null}
</div>
- </div>;
+ {this.sidebarHandle}
+ </div>
+ );
}
- sidebarWidth = () => !this.SidebarShown ? 0 :
- this._previewWidth ? PDFBox.openSidebarWidth :
- (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)
+ sidebarWidth = () =>
+ !this.SidebarShown ? 0 : PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth));
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" });
+ funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
+ funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
//funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
- }
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
+ };
@computed get renderTitleBox() {
- const classname = "pdfBox" + (this.props.isContentActive() ? "-interactive" : "");
- return <div className={classname} >
- <div className="pdfBox-title-outer">
- <strong className="pdfBox-title" >{this.props.Document.title}</strong>
+ const classname = 'pdfBox' + (this.props.isContentActive() ? '-interactive' : '');
+ return (
+ <div className={classname}>
+ <div className="pdfBox-title-outer">
+ <strong className="pdfBox-title">{StrCast(this.props.Document.title)}</strong>
+ </div>
</div>
- </div>;
+ );
}
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@observable _showSidebar = false;
- @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ @computed get SidebarShown() {
+ return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
+ }
+ @computed get sidebarHandle() {
+ return (
+ <div
+ className="pdfBox-sidebarBtn"
+ key="sidebar"
+ title="Toggle Sidebar"
+ style={{
+ display: !this.props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+ }}
+ onPointerDown={e => this.sidebarBtnDown(e, true)}>
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ </div>
+ );
+ }
- contentScaling = () => 1;
isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected();
@computed get renderPdfView() {
TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
- const scale = previewScale * (this.props.scaling?.() || 1);
- return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}
- style={{
- display: this.props.thumbShown?.() ? "none" : undefined,
- height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ?
- NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined
- }}>
- <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
- <div style={{
- width: `calc(${100 / scale}% - ${(this.sidebarWidth() + PDFBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`,
- height: `${100 / scale}%`,
- transform: `scale(${scale})`,
- position: "absolute",
- transformOrigin: "top left",
- top: 0
- }}>
- <PDFViewer {...this.props}
- rootDoc={this.rootDoc}
- layoutDoc={this.layoutDoc}
- dataDoc={this.dataDoc}
- pdf={this._pdf!}
- url={this.pdfUrl!.url.pathname}
- isContentActive={this.isPdfContentActive}
- anchorMenuClick={this.anchorMenuClick}
- loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
- setPdfViewer={this.setPdfViewer}
- addDocument={this.addDocument}
- moveDocument={this.moveDocument}
- removeDocument={this.removeDocument}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- startupLive={true}
- ContentScaling={returnOne}
- />
+ const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+ return (
+ <div
+ className={'pdfBox'}
+ onContextMenu={this.specificContextMenu}
+ style={{
+ display: this.props.thumbShown?.() ? 'none' : undefined,
+ height: this.props.Document._scrollTop && !this.Document._fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined,
+ }}>
+ <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
+ <div
+ style={{
+ width: `calc(${100 / scale}% - ${(this.sidebarWidth() / scale) * (this._previewWidth ? scale : 1)}px)`,
+ height: `${100 / scale}%`,
+ transform: `scale(${scale})`,
+ position: 'absolute',
+ transformOrigin: 'top left',
+ top: 0,
+ }}>
+ <PDFViewer
+ {...this.props}
+ rootDoc={this.rootDoc}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ pdf={this._pdf!}
+ url={this.pdfUrl!.url.pathname}
+ isContentActive={this.isPdfContentActive}
+ anchorMenuClick={this.anchorMenuClick}
+ loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
+ setPdfViewer={this.setPdfViewer}
+ addDocument={this.addDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.removeDocument}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ crop={this.crop}
+ />
+ </div>
+ <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.layoutDoc[WidthSym]()}%` }}>
+ <SidebarAnnos
+ ref={this._sidebarRef}
+ {...this.props}
+ rootDoc={this.rootDoc}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ setHeight={emptyFunction}
+ nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+ showSidebar={this.SidebarShown}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ sidebarAddDocument={this.sidebarAddDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.removeDocument}
+ />
+ </div>
+ {this.settingsPanel()}
</div>
- <SidebarAnnos ref={this._sidebarRef}
- {...this.props}
- rootDoc={this.rootDoc}
- layoutDoc={this.layoutDoc}
- dataDoc={this.dataDoc}
- setHeight={emptyFunction}
- nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
- showSidebar={this.SidebarShown}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- sidebarAddDocument={this.sidebarAddDocument}
- moveDocument={this.moveDocument}
- removeDocument={this.removeDocument}
- />
- {this.settingsPanel()}
- </div>;
+ );
}
static pdfcache = new Map<string, Pdfjs.PDFDocumentProxy>();
@@ -316,12 +513,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const href = this.pdfUrl?.url.href;
if (href) {
- if (PDFBox.pdfcache.get(href)) setTimeout(action(() => this._pdf = PDFBox.pdfcache.get(href)));
+ if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
else {
if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
- PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, this._pdf = pdf)));
+ PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf))));
}
}
return this.renderTitleBox;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.scss b/src/client/views/nodes/RecordingBox/ProgressBar.scss
new file mode 100644
index 000000000..28ad25ffa
--- /dev/null
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.scss
@@ -0,0 +1,123 @@
+
+.progressbar {
+ touch-action: none;
+ vertical-align: middle;
+ text-align: center;
+
+ align-items: center;
+ cursor: default;
+
+
+ position: absolute;
+ display: flex;
+ justify-content: flex-start;
+ bottom: 2px;
+ width: 99%;
+ height: 30px;
+ background-color: gray;
+
+ &.done {
+ top: 0;
+ width: 0px;
+ height: 5px;
+ background-color: red;
+ z-index: 2;
+ }
+
+ &.mark {
+ top: 0;
+ background-color: transparent;
+ border-right: 2px solid white;
+ z-index: 3;
+ pointer-events: none;
+ }
+}
+
+.progressbar-disabled {
+ cursor: not-allowed;
+}
+
+.progressbar-dragging {
+ cursor: grabbing;
+}
+
+// citation: https://codepen.io/_Master_/pen/PRdjmQ
+@keyframes blinker {
+ from {opacity: 1.0;}
+ to {opacity: 0.0;}
+}
+.blink {
+ text-decoration: blink;
+ animation-name: blinker;
+ animation-duration: 0.6s;
+ animation-iteration-count:infinite;
+ animation-timing-function:ease-in-out;
+ animation-direction: alternate;
+}
+
+.segment {
+ border: 3px solid black;
+ background-color: red;
+ margin: 1px;
+ padding: 0;
+ cursor: pointer;
+ transition-duration: .5s;
+ user-select: none;
+
+ vertical-align: middle;
+ text-align: center;
+}
+
+.segment-expanding {
+border-color: red;
+ background-color: white;
+ transition-duration: 0s;
+ opacity: .75;
+ pointer-events: none;
+}
+
+.segment-expanding:hover {
+ background-color: inherit;
+ cursor: not-allowed;
+}
+
+.segment-disabled {
+ pointer-events: none;
+ opacity: 0.5;
+ transition-duration: 0s;
+ /* Hide the text. */
+ text-indent: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.segment-hide {
+ background-color: inherit;
+ text-align: center;
+ vertical-align: middle;
+ user-select: none;
+}
+
+.segment:first-child {
+ margin-left: 2px;
+}
+.segment:last-child {
+ margin-right: 2px;
+}
+
+.segment:hover {
+ background-color: white;
+}
+
+.segment:hover, .segment-selected {
+ margin: 0px;
+ border: 4px solid red;
+ border-radius: 2px;
+}
+
+.segment-selected {
+ border: 4px solid #202020;
+ background-color: red;
+ opacity: .75;
+ cursor: grabbing;
+}
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
new file mode 100644
index 000000000..1bb2b7c84
--- /dev/null
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
@@ -0,0 +1,301 @@
+import * as React from 'react';
+import { useEffect, useState, useCallback, useRef } from "react"
+import "./ProgressBar.scss"
+import { MediaSegment } from './RecordingView';
+
+interface ProgressBarProps {
+ videos: MediaSegment[],
+ setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
+ orderVideos: boolean,
+ progress: number,
+ recording: boolean,
+ doUndo: boolean,
+ setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>,
+}
+
+interface SegmentBox {
+ endTime: number,
+ startTime: number,
+ order: number,
+}
+interface CurrentHover {
+ index: number,
+ minX: number,
+ maxX: number
+}
+
+export function ProgressBar(props: ProgressBarProps) {
+ const progressBarRef = useRef<HTMLDivElement | null>(null)
+
+ // the actual list of JSX elements rendered as segments
+ const [segments, setSegments] = useState<JSX.Element[]>([]);
+ // array for the order of video segments
+ const [ordered, setOrdered] = useState<SegmentBox[]>([]);
+
+ const [undoStack, setUndoStack] = useState<SegmentBox[]>([]);
+
+ // -1 if no segment is currently being dragged around; else, it is the id of that segment over
+ // NOTE: the id of a segment is its index in the ordered array
+ const [dragged, setDragged] = useState<number>(-1);
+
+ // length of the time removed from the video, in seconds*100
+ const [totalRemovedTime, setTotalRemovedTime] = useState<number>(0);
+
+ // this holds the index of the videoc segment to be removed
+ const [removed, setRemoved] = useState<number>(-1);
+
+ // update the canUndo props based on undo stack
+ useEffect(() => props.setCanUndo?.(undoStack.length > 0), [undoStack.length]);
+
+ // useEffect for undo - brings back the most recently deleted segment
+ useEffect(() => handleUndo(), [props.doUndo])
+ const handleUndo = () => {
+ // get the last element from the undo if it exists
+ if (undoStack.length === 0) return;
+ // get and remove the last element from the undo stack
+ const last = undoStack.lastElement();
+ setUndoStack(prevUndo => prevUndo.slice(0, -1));
+
+ // update the removed time and place element back into ordered
+ setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime));
+ setOrdered(prevOrdered => [...prevOrdered, last]);
+ }
+
+ // useEffect for recording changes - changes style to disabled and adds the "expanding-segment"
+ useEffect(() => {
+ // get segments segment's html using it's id -> make them appeared disabled (or enabled)
+ segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
+ progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
+
+ if (props.recording)
+ setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'fit-content' }}>{props.videos.length + 1}</div>]);
+ }, [props.recording])
+
+
+ // useEffect that updates the segmentsJSX, which is rendered
+ // only updated when ordered is updated or if the user is dragging around a segment
+ useEffect(() => {
+ const totalTime = props.progress * 1000 - totalRemovedTime;
+ const segmentsJSX = ordered.map((seg, i) =>
+ <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
+
+ setSegments(segmentsJSX)
+ }, [dragged, ordered]);
+
+ // useEffect for dragged - update the cursor to be grabbing while grabbing
+ useEffect(() => {
+ progressBarRef.current?.classList.toggle('progressbar-dragging', dragged !== -1);
+ }, [dragged]);
+
+ // to imporve performance, only want to update the CSS width, not re-render the whole JSXList
+ useEffect(() => {
+ if (!props.recording) return
+ const totalTime = props.progress * 1000 - totalRemovedTime;
+ let remainingTime = totalTime;
+ segments.forEach((seg, i) => {
+ // for the last segment, we need to set that directly
+ if (i === segments.length - 1) return;
+ // update remaining time
+ remainingTime -= (ordered[i].endTime - ordered[i].startTime);
+
+ // update the width for this segment
+ const htmlId = seg.props.id;
+ const segmentHtml = document.getElementById(htmlId);
+ if (segmentHtml) segmentHtml.style.width = `${((ordered[i].endTime - ordered[i].startTime) / totalTime) * 100}%`;
+ });
+
+ // update the width of the expanding segment using the remaining time
+ const segExapandHtml = document.getElementById('segment-expanding');
+ if (segExapandHtml)
+ segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
+ }, [props.progress]);
+
+ // useEffect for props.videos - update the ordered array when a new video is added
+ useEffect(() => {
+ // this useEffect fired when the videos are being rearragned to the order
+ // in this case, do nothing.
+ if (props.orderVideos) return;
+
+ const order = props.videos.length - 1;
+ // in this case, a new video is added -> push it onto ordered
+ if (order >= ordered.length) {
+ const { endTime, startTime } = props.videos.lastElement();
+ setOrdered(prevOrdered => {
+ return [...prevOrdered, { endTime, startTime, order }];
+ });
+ }
+
+ // in this case, a video is removed
+ else if (order < ordered.length) {
+ console.warn('warning: video removed from parent');
+ }
+ }, [props.videos]);
+
+ // useEffect for props.orderVideos - matched the order array with the videos array before the export
+ useEffect(() => props.setVideos(vids => ordered.map((seg) => vids[seg.order])), [props.orderVideos]);
+
+ // useEffect for removed - handles logic for removing a segment
+ useEffect(() => {
+ if (removed === -1) return;
+ // update total removed time
+ setTotalRemovedTime(prevRemoved => prevRemoved + (ordered[removed].endTime - ordered[removed].startTime));
+
+ // put the element on the undo stack
+ setUndoStack(prevUndo => [...prevUndo, ordered[removed]]);
+ // remove the segment from the array
+ setOrdered(prevOrdered => prevOrdered.filter((seg, i) => i !== removed));
+ // reset to default/nullish state
+ setRemoved(-1);
+ }, [removed]);
+
+ // returns the new currentHover based on the new index
+ const updateCurrentHover = (segId: number): CurrentHover | null => {
+ // get the segId of the segment that will become the new bounding area
+ const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
+ if (rect == null) return null
+ return {
+ index: segId,
+ minX: rect.x,
+ maxX: rect.x + rect.width,
+ }
+ }
+
+ // pointerdown event for the progress bar
+ const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ // don't move the videobox element
+ e.stopPropagation();
+
+ // if recording, do nothing
+ if (props.recording) return;
+
+ // get the segment the user clicked on to be dragged
+ const clickedSegment = e.target as HTMLDivElement & EventTarget
+
+ // get the profess bar ro add event listeners
+ // don't do anything if null
+ const progressBar = progressBarRef.current
+ if (progressBar == null || clickedSegment.id === progressBar.id) return
+
+ // if holding shift key, let's remove that segment
+ if (e.shiftKey) {
+ const segId = parseInt(clickedSegment.id.split('-')[1]);
+ setRemoved(segId);
+ return
+ }
+
+ // if holding ctrl key and click, let's undo that segment #hiddenfeature lol
+ if (e.ctrlKey) {
+ handleUndo();
+ return;
+ }
+
+ // if we're here, the user is dragging a segment around
+ // let the progress bar capture all the pointer events until the user releases (pointerUp)
+ const ptrId = e.pointerId;
+ progressBar.setPointerCapture(ptrId)
+
+ const rect = clickedSegment.getBoundingClientRect()
+ // id for segment is like 'segment-1' or 'segment-10',
+ // so this works to get the id
+ const segId = parseInt(clickedSegment.id.split('-')[1])
+ // set the selected segment to be the one dragged
+ setDragged(segId)
+
+ // this is the logic for storing the lower X bound and upper X bound to know
+ // whether a swap is needed between two segments
+ let currentHover: CurrentHover = {
+ index: segId,
+ minX: rect.x,
+ maxX: rect.x + rect.width,
+ }
+
+ // create the floating segment that tracks the cursor
+ const detchedSegment = document.createElement("div")
+ initDeatchSegment(detchedSegment, rect);
+
+ const updateSegmentOrder = (event: PointerEvent): void => {
+ event.stopPropagation();
+ event.preventDefault();
+
+ // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
+ if (!progressBar.hasPointerCapture(ptrId)) {
+ placeSegmentandCleanup();
+ return;
+ }
+
+ followCursor(event, detchedSegment);
+
+ const curX = event.clientX;
+ // handle the left bound
+ if (curX < currentHover.minX && currentHover.index > 0) {
+ swapSegments(currentHover.index, currentHover.index - 1)
+ currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover
+ }
+ // handle the right bound
+ else if (curX > currentHover.maxX && currentHover.index < segments.length - 1) {
+ swapSegments(currentHover.index, currentHover.index + 1)
+ currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover
+ }
+ }
+
+ // handles when the user is done dragging the segment (pointerUp)
+ const placeSegmentandCleanup = (event?: PointerEvent): void => {
+ event?.stopPropagation();
+ event?.preventDefault();
+ // if they put the segment outside of the bounds, remove it
+ if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight))
+ setRemoved(currentHover.index);
+
+ // remove the update event listener for pointermove
+ progressBar.removeEventListener('pointermove', updateSegmentOrder);
+ // remove the floating segment from the DOM
+ detchedSegment.remove();
+ // dragged is -1 is equiv to nothing being dragged, so the normal state
+ // so this will place the segment in it's location and update the segment bar
+ setDragged(-1);
+ }
+
+ // event listeners that allow the user to drag and release the floating segment
+ progressBar.addEventListener('pointermove', updateSegmentOrder);
+ progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true });
+ }
+
+ const swapSegments = (oldIndex: number, newIndex: number) => {
+ if (newIndex == null) return;
+ setOrdered(prevOrdered => {
+ const temp = { ...prevOrdered[oldIndex] }
+ prevOrdered[oldIndex] = prevOrdered[newIndex]
+ prevOrdered[newIndex] = temp
+ return prevOrdered
+ });
+ // update visually where the segment is hovering over
+ setDragged(newIndex);
+ }
+
+ // functions for the floating segment that tracks the cursor while grabbing it
+ const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
+ dot.classList.add("segment-selected");
+ dot.style.transitionDuration = '0s';
+ dot.style.position = 'absolute';
+ dot.style.zIndex = '999';
+ dot.style.width = `${rect.width}px`;
+ dot.style.height = `${rect.height}px`;
+ dot.style.left = `${rect.x}px`;
+ dot.style.top = `${rect.y}px`;
+ dot.draggable = false;
+ document.body.append(dot);
+ }
+ const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
+ // event.stopPropagation()
+ const { width, height } = dot.getBoundingClientRect();
+ dot.style.left = `${event.clientX - width / 2}px`;
+ dot.style.top = `${event.clientY - height / 2}px`;
+ }
+
+
+ return (
+ <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
+ {segments}
+ </div>
+ )
+} \ No newline at end of file
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
new file mode 100644
index 000000000..0ff7c4292
--- /dev/null
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -0,0 +1,58 @@
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { VideoField } from "../../../../fields/URLField";
+import { Upload } from "../../../../server/SharedMediaTypes";
+import { ViewBoxBaseComponent } from "../../DocComponent";
+import { FieldView } from "../FieldView";
+import { VideoBox } from "../VideoBox";
+import { RecordingView } from './RecordingView';
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { Presentation } from "../../../util/TrackMovements";
+import { Doc } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+
+
+@observer
+export class RecordingBox extends ViewBoxBaseComponent() {
+
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); }
+
+ private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+
+ constructor(props: any) {
+ super(props);
+ }
+
+ componentDidMount() {
+ Doc.SetNativeWidth(this.dataDoc, 1280);
+ Doc.SetNativeHeight(this.dataDoc, 720);
+ }
+
+ @observable result: Upload.AccessPathInfo | undefined = undefined
+ @observable videoDuration: number | undefined = undefined
+
+ @action
+ setVideoDuration = (duration: number) => {
+ this.videoDuration = duration
+ }
+
+ @action
+ setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
+ this.result = info
+ this.dataDoc.type = DocumentType.VID;
+ this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration;
+
+ this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
+ this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client);
+ this.dataDoc[this.fieldKey + "-recorded"] = true;
+ // stringify the presentation and store it
+ presentation?.movements && (this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(presentation));
+ }
+
+ render() {
+ return <div className="recordingBox" ref={this._ref}>
+ {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id] || ''} />}
+ </div>;
+ }
+}
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss
new file mode 100644
index 000000000..2e6f6bc26
--- /dev/null
+++ b/src/client/views/nodes/RecordingBox/RecordingView.scss
@@ -0,0 +1,214 @@
+video {
+ // flex: 100%;
+ width: 100%;
+ // min-height: 400px;
+ //height: auto;
+ height: 100%;
+ //display: block;
+ object-fit: cover;
+ background-color: black;
+}
+
+button {
+ margin: 0 .5rem
+}
+
+.recording-container {
+ height: 100%;
+ width: 100%;
+ // display: flex;
+ pointer-events: all;
+ background-color: black;
+}
+
+.video-wrapper {
+ // max-width: 600px;
+ // max-width: 700px;
+ // position: relative;
+ display: flex;
+ justify-content: center;
+ // overflow: hidden;
+ border-radius: 10px;
+ margin: 0;
+}
+
+.video-wrapper:hover .controls {
+ bottom: 34.5px;
+ transform: translateY(0%);
+ opacity: 100%;
+}
+
+.controls {
+ display: flex;
+ align-items: center;
+ justify-content: space-evenly;
+ position: absolute;
+ // padding: 14px;
+ //width: 100%;
+ max-width: 500px;
+ // max-height: 20%;
+ flex-wrap: wrap;
+ background: rgba(255, 255, 255, 0.25);
+ box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(4px);
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ // transform: translateY(150%);
+ transition: all 0.3s ease-in-out;
+ // opacity: 0%;
+ bottom: 34.5px;
+ height: 60px;
+ right: 2px;
+ // bottom: -150px;
+}
+
+.controls:active {
+ bottom: 40px;
+ // bottom: -150px;
+}
+
+.actions button {
+ background: none;
+ border: none;
+ outline: none;
+ cursor: pointer;
+}
+
+.actions button i {
+ background-color: none;
+ color: white;
+ font-size: 30px;
+}
+
+
+.velocity {
+ appearance: none;
+ background: none;
+ color: white;
+ outline: none;
+ border: none;
+ text-align: center;
+ font-size: 16px;
+}
+
+.mute-btn {
+ background: none;
+ border: none;
+ outline: none;
+ cursor: pointer;
+}
+
+.mute-btn i {
+ background-color: none;
+ color: white;
+ font-size: 20px;
+}
+
+.recording-sign {
+ height: 20px;
+ width: auto;
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ align-items: center;
+ justify-content: center;
+
+ .timer {
+ font-size: 15px;
+ color: white;
+ margin: 0;
+ }
+
+ .dot {
+ height: 15px;
+ width: 15px;
+ margin: 5px;
+ background-color: red;
+ border-radius: 50%;
+ display: inline-block;
+ }
+}
+
+.controls-inner-container {
+ display: flex;
+ flex-direction: row;
+ align-content: center;
+ position: relative;
+}
+
+.record-button-wrapper {
+ width: 35px;
+ height: 35px;
+ font-size: 0;
+ background-color: grey;
+ border: 0px;
+ border-radius: 35px;
+ margin: 10px;
+ display: flex;
+ justify-content: center;
+
+ .record-button {
+ background-color: red;
+ border: 0px;
+ border-radius: 50%;
+ height: 80%;
+ width: 80%;
+ align-self: center;
+ margin: 0;
+
+ &:hover {
+ height: 85%;
+ width: 85%;
+ }
+ }
+
+ .stop-button {
+ background-color: red;
+ border: 0px;
+ border-radius: 10%;
+ height: 70%;
+ width: 70%;
+ align-self: center;
+ margin: 0;
+
+
+ // &:hover {
+ // width: 40px;
+ // height: 40px
+ // }
+ }
+
+}
+
+.options-wrapper {
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ align-content: center;
+ position: relative;
+ top: 0;
+ bottom: 0;
+
+ &.video-edit-wrapper {
+
+ // right: 50% - 15;
+
+ .track-screen {
+ font-weight: 200;
+ }
+
+ }
+
+ &.track-screen-wrapper {
+
+ // right: 50% - 30;
+
+ .track-screen {
+ font-weight: 200;
+ color: aqua;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
new file mode 100644
index 000000000..ec5917b9e
--- /dev/null
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -0,0 +1,271 @@
+import * as React from 'react';
+import "./RecordingView.scss";
+import { useEffect, useRef, useState } from "react";
+import { ProgressBar } from "./ProgressBar"
+import { MdBackspace } from 'react-icons/md';
+import { FaCheckCircle } from 'react-icons/fa';
+import { IconContext } from "react-icons";
+import { Networking } from '../../../Network';
+import { Upload } from '../../../../server/SharedMediaTypes';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { Presentation, TrackMovements } from '../../../util/TrackMovements';
+
+export interface MediaSegment {
+ videoChunks: any[],
+ endTime: number,
+ startTime: number,
+ presentation?: Presentation,
+}
+
+interface IRecordingViewProps {
+ setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void
+ setDuration: (seconds: number) => void
+ id: string
+}
+
+const MAXTIME = 100000;
+
+export function RecordingView(props: IRecordingViewProps) {
+
+ const [recording, setRecording] = useState(false);
+ const recordingTimerRef = useRef<number>(0);
+ const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
+ const [playing, setPlaying] = useState(false);
+ const [progress, setProgress] = useState(0);
+
+ // acts as a "refresh state" to tell progressBar when to undo
+ const [doUndo, setDoUndo] = useState(false);
+ // whether an undo can occur or not
+ const [canUndo, setCanUndo] = useState(false);
+
+ const [videos, setVideos] = useState<MediaSegment[]>([]);
+ const [orderVideos, setOrderVideos] = useState<boolean>(false);
+ const videoRecorder = useRef<MediaRecorder | null>(null);
+ const videoElementRef = useRef<HTMLVideoElement | null>(null);
+
+ const [finished, setFinished] = useState<boolean>(false);
+ const [trackScreen, setTrackScreen] = useState<boolean>(false);
+
+
+
+ const DEFAULT_MEDIA_CONSTRAINTS = {
+ video: {
+ width: 1280,
+ height: 720,
+
+ },
+ audio: {
+ echoCancellation: true,
+ noiseSuppression: true,
+ sampleRate: 44100
+ }
+ };
+
+ useEffect(() => {
+ if (finished) {
+ // make the total presentation that'll match the concatted video
+ let concatPres = trackScreen && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+
+ // this async function uses the server to create the concatted video and then sets the result to it's accessPaths
+ (async () => {
+ const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() }));
+
+ // upload the segments to the server and get their server access paths
+ const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles))
+ .map(res => (res.result instanceof Error) ? '' : res.result.accessPaths.agnostic.server)
+
+ // concat the segments together using post call
+ const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths);
+ !(result instanceof Error) ? props.setResult(result, concatPres || undefined) : console.error("video conversion failed");
+ })();
+ }
+ }, [videos]);
+
+ // this will call upon the progress bar to edit videos to be in the correct order
+ useEffect(() => {
+ finished && setOrderVideos(true);
+ }, [finished]);
+
+ // check if the browser supports media devices on first load
+ useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, []);
+
+ useEffect(() => {
+ let interval: any = null;
+ if (recording) {
+ interval = setInterval(() => {
+ setRecordingTimer(unit => unit + 1);
+ }, 10);
+ } else if (!recording && recordingTimer !== 0) {
+ clearInterval(interval);
+ }
+ return () => clearInterval(interval);
+ }, [recording]);
+
+ useEffect(() => {
+ setVideoProgressHelper(recordingTimer)
+ recordingTimerRef.current = recordingTimer;
+ }, [recordingTimer]);
+
+ const setVideoProgressHelper = (progress: number) => {
+ const newProgress = (progress / MAXTIME) * 100;
+ setProgress(newProgress);
+ }
+
+ const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
+ const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
+
+ videoElementRef.current!.src = "";
+ videoElementRef.current!.srcObject = stream;
+ videoElementRef.current!.muted = true;
+
+ return stream;
+ }
+
+ const record = async () => {
+ // don't need to start a new stream every time we start recording a new segment
+ if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream());
+
+ // temporary chunks of video
+ let videoChunks: any = [];
+
+ videoRecorder.current.ondataavailable = (event: any) => {
+ if (event.data.size > 0) videoChunks.push(event.data);
+ };
+
+ videoRecorder.current.onstart = (event: any) => {
+ setRecording(true);
+ // start the recording api when the video recorder starts
+ trackScreen && TrackMovements.Instance.start();
+ };
+
+ videoRecorder.current.onstop = () => {
+ // if we have a last portion
+ if (videoChunks.length > 1) {
+ // append the current portion to the video pieces
+ const nextVideo = {
+ videoChunks,
+ endTime: recordingTimerRef.current,
+ startTime: videos?.lastElement()?.endTime || 0
+ };
+
+ // depending on if a presenation exists, add it to the video
+ const presentation = TrackMovements.Instance.yieldPresentation();
+ setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]);
+ }
+
+ // reset the temporary chunks
+ videoChunks = [];
+ setRecording(false);
+ }
+
+ videoRecorder.current.start(200);
+ }
+
+
+ // if this is called, then we're done recording all the segments
+ const finish = (e: React.PointerEvent) => {
+ e.stopPropagation();
+
+ // call stop on the video recorder if active
+ videoRecorder.current?.state !== "inactive" && videoRecorder.current?.stop();
+
+ // end the streams (audio/video) to remove recording icon
+ const stream = videoElementRef.current!.srcObject;
+ stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop());
+
+ // finish/clear the recoringApi
+ TrackMovements.Instance.finish();
+
+ // this will call upon progessbar to update videos to be in the correct order
+ setFinished(true);
+ }
+
+ const pause = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ // if recording, then this is just a new segment
+ videoRecorder.current?.state === "recording" && videoRecorder.current.stop();
+ }
+
+ const start = (e: React.PointerEvent) => {
+ setupMoveUpEvents({}, e, returnTrue, returnFalse, e => {
+ // start recording if not already recording
+ if (!videoRecorder.current || videoRecorder.current.state === "inactive") record();
+
+ return true; // cancels propagation to documentView to avoid selecting it.
+ }, false, false);
+ }
+
+ const undoPrevious = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ setDoUndo(prev => !prev);
+ }
+
+ const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); };
+
+ const millisecondToMinuteSecond = (milliseconds: number) => {
+ const toTwoDigit = (digit: number) => {
+ return String(digit).length == 1 ? "0" + digit : digit
+ }
+ const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
+ return toTwoDigit(minutes) + " : " + toTwoDigit(seconds);
+ }
+
+ return (
+ <div className="recording-container">
+ <div className="video-wrapper">
+ <video id={`video-${props.id}`}
+ autoPlay
+ muted
+ onTimeUpdate={() => handleOnTimeUpdate()}
+ ref={videoElementRef}
+ />
+ <div className="recording-sign">
+ <span className="dot" />
+ <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
+ </div>
+ <div className="controls">
+
+ <div className="controls-inner-container">
+ <div className="record-button-wrapper">
+ {recording ?
+ <button className="stop-button" onPointerDown={pause} /> :
+ <button className="record-button" onPointerDown={start} />
+ }
+ </div>
+
+ {!recording && (videos.length > 0 ?
+
+ <div className="options-wrapper video-edit-wrapper">
+ <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons", style: { display: canUndo ? 'inherit' : 'none' } }}>
+ <MdBackspace onPointerDown={undoPrevious} />
+ </IconContext.Provider>
+ <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
+ <FaCheckCircle onPointerDown={finish} />
+ </IconContext.Provider>
+ </div>
+
+ : <div className="options-wrapper track-screen-wrapper">
+ <label className="track-screen">
+ <input type="checkbox" checked={trackScreen} onChange={(e) => { setTrackScreen(e.target.checked) }} />
+ <span className="checkmark"></span>
+ Track Screen
+ </label>
+ </div>)}
+
+ </div>
+
+ </div>
+
+ <ProgressBar
+ videos={videos}
+ setVideos={setVideos}
+ orderVideos={orderVideos}
+ progress={progress}
+ recording={recording}
+ doUndo={doUndo}
+ setCanUndo={setCanUndo}
+ />
+ </div>
+ </div>)
+} \ No newline at end of file
diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts
new file mode 100644
index 000000000..ff21eaed6
--- /dev/null
+++ b/src/client/views/nodes/RecordingBox/index.ts
@@ -0,0 +1,2 @@
+export * from './RecordingView'
+export * from './RecordingBox' \ No newline at end of file
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index dbb567d3a..76a24d831 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,32 +1,31 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
// import { Canvas } from '@react-three/fiber';
-import { computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import { computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
// import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
-import { DateField } from "../../../fields/DateField";
-import { Doc, HeightSym, WidthSym } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, NumCast } from "../../../fields/Types";
-import { AudioField, VideoField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, OmitKeys, returnFalse, returnOne } from "../../../Utils";
-import { DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { Networking } from "../../Network";
-import { CaptureManager } from "../../util/CaptureManager";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
-import { ContextMenu } from "../ContextMenu";
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
+import { DateField } from '../../../fields/DateField';
+import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { ComputedField } from '../../../fields/ScriptField';
+import { Cast, NumCast } from '../../../fields/Types';
+import { AudioField, VideoField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, OmitKeys, returnFalse, returnOne } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Networking } from '../../Network';
+import { CaptureManager } from '../../util/CaptureManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { ContextMenu } from '../ContextMenu';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
-import "./ScreenshotBox.scss";
-import { VideoBox } from "./VideoBox";
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import './ScreenshotBox.scss';
+import { VideoBox } from './VideoBox';
declare class MediaRecorder {
- constructor(e: any, options?: any); // whatever MediaRecorder has
+ constructor(e: any, options?: any); // whatever MediaRecorder has
}
// interface VideoTileProps {
@@ -107,12 +106,16 @@ declare class MediaRecorder {
@observer
export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(ScreenshotBox, fieldKey);
+ }
private _audioRec: any;
private _videoRec: any;
@observable private _videoRef: HTMLVideoElement | null = null;
@observable _screenCapture = false;
- @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
+ @computed get recordingStart() {
+ return Cast(this.dataDoc[this.props.fieldKey + '-recordingStart'], DateField)?.date.getTime();
+ }
constructor(props: any) {
super(props);
@@ -126,11 +129,9 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
}
}
getAnchor = () => {
- const startTime = Cast(this.layoutDoc._currentTimecode, "number", null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
- return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow", "_timecodeToHide",
- startTime, startTime === undefined ? undefined : startTime + 3)
- || this.rootDoc;
- }
+ const startTime = Cast(this.layoutDoc._currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
+ return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow', '_timecodeToHide', startTime, startTime === undefined ? undefined : startTime + 3) || this.rootDoc;
+ };
videoLoad = () => {
const aspect = this._videoRef!.videoWidth / this._videoRef!.videoHeight;
@@ -141,7 +142,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect);
this.layoutDoc._height = (this.layoutDoc[WidthSym]() || 0) / aspect;
}
- }
+ };
componentDidMount() {
this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0;
@@ -159,33 +160,37 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
}
componentWillUnmount() {
const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
+ ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
}
specificContextMenu = (e: React.MouseEvent): void => {
- const subitems = [{ description: "Screen Capture", event: this.toggleRecording, icon: "expand-arrows-alt" as any }];
- ContextMenu.Instance.addItem({ description: "Options...", subitems, icon: "video" });
- }
+ const subitems = [{ description: 'Screen Capture', event: this.toggleRecording, icon: 'expand-arrows-alt' as any }];
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems, icon: 'video' });
+ };
@computed get content() {
- if (this.rootDoc.videoWall) return (null);
- return <video className={"videoBox-content"} key="video"
- ref={r => {
- this._videoRef = r;
- setTimeout(() => {
- if (this.rootDoc.mediaState === "pendingRecording" && this._videoRef) {
- this.toggleRecording();
- }
- }, 100);
- }}
- autoPlay={this._screenCapture}
- style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
- onCanPlay={this.videoLoad}
- controls={true}
- onClick={e => e.preventDefault()}>
- <source type="video/mp4" />
- Not supported.
- </video>;
+ if (this.rootDoc.videoWall) return null;
+ return (
+ <video
+ className={'videoBox-content'}
+ key="video"
+ ref={r => {
+ this._videoRef = r;
+ setTimeout(() => {
+ if (this.rootDoc.mediaState === 'pendingRecording' && this._videoRef) {
+ this.toggleRecording();
+ }
+ }, 100);
+ }}
+ autoPlay={this._screenCapture}
+ style={{ width: this._screenCapture ? '100%' : undefined, height: this._screenCapture ? '100%' : undefined }}
+ onCanPlay={this.videoLoad}
+ controls={true}
+ onClick={e => e.preventDefault()}>
+ <source type="video/mp4" />
+ Not supported.
+ </video>
+ );
}
// _numScreens = 5;
@@ -209,7 +214,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
// {screens}
// </ Canvas>;
// }
- return (null);
+ return null;
}
toggleRecording = async () => {
if (!this._screenCapture) {
@@ -219,31 +224,33 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this._audioRec.onstop = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(aud_chunks);
if (!(result instanceof Error)) {
- this.dataDoc[this.props.fieldKey + "-audio"] = new AudioField(result.accessPaths.agnostic.client);
+ this.dataDoc[this.props.fieldKey + '-audio'] = new AudioField(result.accessPaths.agnostic.client);
}
};
this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
this._videoRec = new MediaRecorder(this._videoRef!.srcObject);
const vid_chunks: any = [];
- this._videoRec.onstart = () => this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date());
+ this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '-recordingStart'] = new DateField(new Date()));
this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data);
this._videoRec.onstop = async (e: any) => {
+ console.log('screenshotbox: upload');
const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
const [{ result }] = await Networking.UploadFilesToServer(file);
- this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this.recordingStart!) / 1000;
- if (!(result instanceof Error)) { // convert this screenshotBox into normal videoBox
+ this.dataDoc[this.fieldKey + '-duration'] = (new Date().getTime() - this.recordingStart!) / 1000;
+ if (!(result instanceof Error)) {
+ // convert this screenshotBox into normal videoBox
this.dataDoc.type = DocumentType.VID;
this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey);
this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = undefined;
this.layoutDoc._fitWidth = undefined;
this.dataDoc[this.props.fieldKey] = new VideoField(result.accessPaths.agnostic.client);
- } else alert("video conversion failed");
+ } else alert('video conversion failed');
};
this._audioRec.start();
this._videoRec.start();
runInAction(() => {
this._screenCapture = true;
- this.dataDoc.mediaState = "recording";
+ this.dataDoc.mediaState = 'recording';
});
DocUtils.ActiveRecordings.push(this);
} else {
@@ -251,81 +258,86 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this._videoRec?.stop();
runInAction(() => {
this._screenCapture = false;
- this.dataDoc.mediaState = "paused";
+ this.dataDoc.mediaState = 'paused';
});
const ind = DocUtils.ActiveRecordings.indexOf(this);
- ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
+ ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
CaptureManager.Instance.open(this.rootDoc);
}
- }
+ };
setupDictation = () => {
- if (this.dataDoc[this.fieldKey + "-dictation"]) return;
- const dictationText = CurrentUserUtils.GetNewTextDoc("dictation",
- NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10,
- NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
+ if (this.dataDoc[this.fieldKey + '-dictation']) return;
+ const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
dictationText._autoHeight = false;
const dictationTextProto = Doc.GetProto(dictationText);
dictationTextProto.recordingSource = this.dataDoc;
dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
- dictationTextProto.mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
- this.dataDoc[this.fieldKey + "-dictation"] = dictationText;
- }
+ dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState');
+ this.dataDoc[this.fieldKey + '-dictation'] = dictationText;
+ };
contentFunc = () => [this.threed, this.content];
- videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], this.layoutDoc[WidthSym]()) * this.props.PanelWidth();
+ videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth'], this.layoutDoc[WidthSym]())) * this.props.PanelWidth();
formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
render() {
TraceMobx();
- return <div className="videoBox" onContextMenu={this.specificContextMenu} style={{ width: "100%", height: "100%" }} >
- <div className="videoBox-viewer" >
- <div style={{ position: "relative", height: this.videoPanelHeight() }}>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- PanelHeight={this.videoPanelHeight}
- PanelWidth={this.props.PanelWidth}
- focus={this.props.focus}
- isSelected={this.props.isSelected}
- isAnnotationOverlay={true}
- select={emptyFunction}
- isContentActive={returnFalse}
- scaling={returnOne}
- whenChildContentsActiveChanged={emptyFunction}
- removeDocument={returnFalse}
- moveDocument={returnFalse}
- addDocument={returnFalse}
- CollectionView={undefined}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- renderDepth={this.props.renderDepth + 1}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
- {this.contentFunc}
- </CollectionFreeFormView></div>
- <div style={{ position: "relative", height: this.formattedPanelHeight() }}>
- {!(this.dataDoc[this.fieldKey + "-dictation"] instanceof Doc) ? (null) :
- <FormattedTextBox {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- Document={this.dataDoc[this.fieldKey + "-dictation"]}
- fieldKey={"text"}
- PanelHeight={this.formattedPanelHeight}
+ return (
+ <div className="videoBox" onContextMenu={this.specificContextMenu} style={{ width: '100%', height: '100%' }}>
+ <div className="videoBox-viewer">
+ <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
+ <CollectionFreeFormView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+ PanelHeight={this.videoPanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
isAnnotationOverlay={true}
select={emptyFunction}
- isContentActive={emptyFunction}
- scaling={returnOne}
- xPadding={25}
- yPadding={10}
+ isContentActive={returnFalse}
+ NativeDimScaling={returnOne}
whenChildContentsActiveChanged={emptyFunction}
removeDocument={returnFalse}
moveDocument={returnFalse}
addDocument={returnFalse}
CollectionView={undefined}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
- </FormattedTextBox>}
+ {this.contentFunc}
+ </CollectionFreeFormView>
+ </div>
+ <div style={{ position: 'relative', height: this.formattedPanelHeight() }}>
+ {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : (
+ <FormattedTextBox
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+ Document={this.dataDoc[this.fieldKey + '-dictation']}
+ fieldKey={'text'}
+ PanelHeight={this.formattedPanelHeight}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ isContentActive={emptyFunction}
+ NativeDimScaling={returnOne}
+ xPadding={25}
+ yPadding={10}
+ whenChildContentsActiveChanged={emptyFunction}
+ removeDocument={returnFalse}
+ moveDocument={returnFalse}
+ addDocument={returnFalse}
+ CollectionView={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}></FormattedTextBox>
+ )}
+ </div>
</div>
+ {!this.props.isSelected() ? null : (
+ <div className="screenshotBox-uiButtons">
+ <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording}>
+ <FontAwesomeIcon icon="file" size="lg" />
+ </div>
+ </div>
+ )}
</div>
- {!this.props.isSelected() ? (null) : <div className="screenshotBox-uiButtons">
- <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} >
- <FontAwesomeIcon icon="file" size="lg" />
- </div>
- </div>}
- </div >;
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 366c3fc2f..1c9b0bc0e 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,39 +1,39 @@
-import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
-import "@webscopeio/react-textarea-autocomplete/style.css";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { ScriptField } from "../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { returnEmptyString } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
-import { InteractionUtils } from "../../util/InteractionUtils";
-import { CompileScript, ScriptParam } from "../../util/Scripting";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { ScriptManager } from "../../util/ScriptManager";
-import { ContextMenu } from "../ContextMenu";
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
-import { EditableView } from "../EditableView";
-import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import { OverlayView } from "../OverlayView";
-import { DocumentIconContainer } from "./DocumentIcon";
-import "./ScriptingBox.scss";
-const _global = (window /* browser */ || global /* node */) as any;
+let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { ScriptField } from '../../../fields/ScriptField';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { returnEmptyString } from '../../../Utils';
+import { DragManager } from '../../util/DragManager';
+import { InteractionUtils } from '../../util/InteractionUtils';
+import { CompileScript, ScriptParam } from '../../util/Scripting';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { ScriptManager } from '../../util/ScriptManager';
+import { ContextMenu } from '../ContextMenu';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { EditableView } from '../EditableView';
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { OverlayView } from '../OverlayView';
+import { DocumentIconContainer } from './DocumentIcon';
+import './ScriptingBox.scss';
+const _global = (window /* browser */ || global) /* node */ as any;
@observer
export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
-
private dropDisposer?: DragManager.DragDropDisposer;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(ScriptingBox, fieldStr);
+ }
private _overlayDisposer?: () => void;
private _caretPos = 0;
- @observable private _errorMessage: string = "";
+ @observable private _errorMessage: string = '';
@observable private _applied: boolean = false;
@observable private _function: boolean = false;
@observable private _spaced: boolean = false;
@@ -42,12 +42,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
@observable private _scriptingDescriptions: any = ScriptingGlobals.getDescriptions();
@observable private _scriptingParams: any = ScriptingGlobals.getParameters();
- @observable private _currWord: string = "";
+ @observable private _currWord: string = '';
@observable private _suggestions: string[] = [];
@observable private _suggestionBoxX: number = 0;
@observable private _suggestionBoxY: number = 0;
- @observable private _lastChar: string = "";
+ @observable private _lastChar: string = '';
@observable private _suggestionRef: any = React.createRef();
@observable private _scriptTextRef: any = React.createRef();
@@ -55,51 +55,80 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
@observable private _selection: any = 0;
@observable private _paramSuggestion: boolean = false;
- @observable private _scriptSuggestedParams: any = "";
- @observable private _scriptParamsText: any = "";
+ @observable private _scriptSuggestedParams: any = '';
+ @observable private _scriptParamsText: any = '';
constructor(props: any) {
super(props);
+ if (!this.compileParams.length) {
+ const params = ScriptCast(this.rootDoc[this.props.fieldKey])?.script.options.params as { [key: string]: any };
+ if (params) {
+ this.compileParams = Array.from(Object.keys(params))
+ .filter(p => !p.startsWith('_'))
+ .map(key => key + ':' + params[key]);
+ }
+ }
}
// vars included in fields that store parameters types and names and the script itself
- @computed({ keepAlive: true }) get paramsNames() { return this.compileParams.map(p => p.split(":")[0].trim()); }
- @computed({ keepAlive: true }) get paramsTypes() { return this.compileParams.map(p => p.split(":")[1].trim()); }
- @computed({ keepAlive: true }) get rawScript() { return StrCast(this.dataDoc[this.props.fieldKey + "-rawScript"], ""); }
- @computed({ keepAlive: true }) get functionName() { return StrCast(this.dataDoc[this.props.fieldKey + "-functionName"], ""); }
- @computed({ keepAlive: true }) get functionDescription() { return StrCast(this.dataDoc[this.props.fieldKey + "-functionDescription"], ""); }
- @computed({ keepAlive: true }) get compileParams() { return Cast(this.dataDoc[this.props.fieldKey + "-params"], listSpec("string"), []); }
+ @computed({ keepAlive: true }) get paramsNames() {
+ return this.compileParams.map(p => p.split(':')[0].trim());
+ }
+ @computed({ keepAlive: true }) get paramsTypes() {
+ return this.compileParams.map(p => p.split(':')[1].trim());
+ }
+ @computed({ keepAlive: true }) get rawScript() {
+ return ScriptCast(this.rootDoc[this.fieldKey])?.script.originalScript ?? '';
+ }
+ @computed({ keepAlive: true }) get functionName() {
+ return StrCast(this.rootDoc[this.props.fieldKey + '-functionName'], '');
+ }
+ @computed({ keepAlive: true }) get functionDescription() {
+ return StrCast(this.rootDoc[this.props.fieldKey + '-functionDescription'], '');
+ }
+ @computed({ keepAlive: true }) get compileParams() {
+ return Cast(this.rootDoc[this.props.fieldKey + '-params'], listSpec('string'), []);
+ }
- set rawScript(value) { this.dataDoc[this.props.fieldKey + "-rawScript"] = value; }
- set functionName(value) { this.dataDoc[this.props.fieldKey + "-functionName"] = value; }
- set functionDescription(value) { this.dataDoc[this.props.fieldKey + "-functionDescription"] = value; }
+ set rawScript(value) {
+ Doc.SetInPlace(this.rootDoc, this.props.fieldKey, new ScriptField(undefined, undefined, value), true);
+ }
+ set functionName(value) {
+ Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionName', value, true);
+ }
+ set functionDescription(value) {
+ Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionDescription', value, true);
+ }
- set compileParams(value) { this.dataDoc[this.props.fieldKey + "-params"] = new List<string>(value); }
+ set compileParams(value) {
+ Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-params', new List<string>(value), true);
+ }
getValue(result: any, descrip: boolean) {
- if (typeof result === "object") {
+ if (typeof result === 'object') {
const text = descrip ? result[1] : result[2];
- return text !== undefined ? text : "";
+ return text !== undefined ? text : '';
} else {
- return "";
+ return '';
}
}
@action
componentDidMount() {
- this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript ?? this.rawScript;
-
- const observer = new _global.ResizeObserver(action((entries: any) => {
- const area = document.querySelector('textarea');
- if (area) {
- for (const { } of entries) {
- const getCaretCoordinates = require('textarea-caret');
- const caret = getCaretCoordinates(area, this._selection);
- this.resetSuggestionPos(caret);
+ this.rawText = this.rawScript;
+ const observer = new _global.ResizeObserver(
+ action((entries: any) => {
+ const area = document.querySelector('textarea');
+ if (area) {
+ for (const {} of entries) {
+ const getCaretCoordinates = require('textarea-caret');
+ const caret = getCaretCoordinates(area, this._selection);
+ this.resetSuggestionPos(caret);
+ }
}
- }
- }));
- observer.observe(document.getElementsByClassName("scriptingBox")[0]);
+ })
+ );
+ observer.observe(document.getElementsByClassName('scriptingBox')[0]);
}
@action
@@ -110,8 +139,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
const top = caret.top;
const x = this.dataDoc.x;
let left = caret.left;
- if ((left + suggestionWidth) > (x + scriptWidth)) {
- const diff = (left + suggestionWidth) - (x + scriptWidth);
+ if (left + suggestionWidth > x + scriptWidth) {
+ const diff = left + suggestionWidth - (x + scriptWidth);
left = left - diff;
}
@@ -123,52 +152,53 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
this._overlayDisposer?.();
}
- protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => { //used for stacking and masonry view
+ protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => {
+ //used for stacking and masonry view
if (ele) {
this.dropDisposer?.();
this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc);
}
- }
+ };
// only included in buttons, transforms scripting UI to a button
@action
onFinish = () => {
- this.rootDoc.layoutKey = "layout";
- }
+ this.rootDoc.layoutKey = 'layout';
+ };
// displays error message
@action
onError = (error: any) => {
- this._errorMessage = error?.message ? error.message : error?.map((entry: any) => entry.messageText).join(" ") || "";
- }
+ this._errorMessage = error?.message ? error.message : error?.map((entry: any) => entry.messageText).join(' ') || '';
+ };
// checks if the script compiles using CompileScript method and inputting params
@action
onCompile = () => {
const params: ScriptParam = {};
- this.compileParams.forEach(p => params[p.split(":")[0].trim()] = p.split(":")[1].trim());
+ this.compileParams.forEach(p => (params[p.split(':')[0].trim()] = p.split(':')[1].trim()));
- const result = CompileScript(this.rawScript, {
+ const result = CompileScript(this.rawText, {
editable: true,
transformer: DocumentIconContainer.getTransformer(),
params,
- typecheck: false
+ typecheck: false,
});
- this.dataDoc[this.fieldKey] = result.compiled ? new ScriptField(result) : undefined;
+ Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : new ScriptField(undefined, undefined, this.rawText), true);
this.onError(result.compiled ? undefined : result.errors);
return result.compiled;
- }
+ };
// checks if the script compiles and then runs the script
@action
onRun = () => {
if (this.onCompile()) {
const bindings: { [name: string]: any } = {};
- this.paramsNames.forEach(key => bindings[key] = this.dataDoc[key]);
+ this.paramsNames.forEach(key => (bindings[key] = this.rootDoc[key]));
// binds vars so user doesnt have to refer to everything as self.<var>
- ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError);
+ ScriptCast(this.rootDoc[this.fieldKey], null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError);
}
- }
+ };
// checks if the script compiles and switches to applied UI
@action
@@ -176,39 +206,39 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
if (this.onCompile()) {
this._applied = true;
}
- }
+ };
@action
onEdit = () => {
- this._errorMessage = "";
+ this._errorMessage = '';
this._applied = false;
this._function = false;
- }
+ };
@action
onSave = () => {
if (this.onCompile()) {
this._function = true;
} else {
- this._errorMessage = "Can not save script, does not compile";
+ this._errorMessage = 'Can not save script, does not compile';
}
- }
+ };
@action
onCreate = () => {
- this._errorMessage = "";
+ this._errorMessage = '';
if (this.functionName.length === 0) {
- this._errorMessage = "Must enter a function name";
+ this._errorMessage = 'Must enter a function name';
return false;
}
- if (this.functionName.indexOf(" ") > 0) {
- this._errorMessage = "Name can not include spaces";
+ if (this.functionName.indexOf(' ') > 0) {
+ this._errorMessage = 'Name can not include spaces';
return false;
}
- if (this.functionName.indexOf(".") > 0) {
+ if (this.functionName.indexOf('.') > 0) {
this._errorMessage = "Name can not include '.'";
return false;
}
@@ -223,173 +253,184 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
this._scriptKeys = ScriptingGlobals.getGlobals();
this._scriptingDescriptions = ScriptingGlobals.getDescriptions();
this._scriptingParams = ScriptingGlobals.getParameters();
- }
+ };
// overlays document numbers (ex. d32) over all documents when clicked on
onFocus = () => {
this._overlayDisposer?.();
this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
- }
+ };
// sets field of the corresponding field key (param name) to be dropped document
@action
onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
- this.dataDoc[fieldKey] = de.complete.docDragData?.droppedDocuments[0];
+ Doc.SetInPlace(this.rootDoc, fieldKey, de.complete.docDragData?.droppedDocuments[0], true);
e.stopPropagation();
- }
+ };
- // deletes a param from all areas in which it is stored
+ // deletes a param from all areas in which it is stored
@action
onDelete = (num: number) => {
- this.dataDoc[this.paramsNames[num]] = undefined;
+ Doc.SetInPlace(this.rootDoc, this.paramsNames[num], undefined, true);
this.compileParams.splice(num, 1);
return true;
- }
+ };
// sets field of the param name to the selected value in drop down box
@action
viewChanged = (e: React.ChangeEvent, name: string) => {
//@ts-ignore
const val = e.target.selectedOptions[0].value;
- this.dataDoc[name] = val[0] === "S" ? val.substring(1) : val[0] === "N" ? parseInt(val.substring(1)) : val.substring(1) === "true";
- }
+ Doc.SetInPlace(this.rootDoc, name, val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true', true);
+ };
// creates a copy of the script document
onCopy = () => {
const copy = Doc.MakeCopy(this.rootDoc, true);
copy.x = NumCast(this.dataDoc.x) + NumCast(this.dataDoc._width);
this.props.addDocument?.(copy);
- }
+ };
// adds option to create a copy to the context menu
specificContextMenu = (): void => {
- const existingOptions = ContextMenu.Instance.findByDescription("Options...");
- const options = existingOptions && "subitems" in existingOptions ? existingOptions.subitems : [];
- options.push({ description: "Create a Copy", event: this.onCopy, icon: "copy" });
- !existingOptions && ContextMenu.Instance.addItem({ description: "Options...", subitems: options, icon: "hand-point-right" });
- }
+ const existingOptions = ContextMenu.Instance.findByDescription('Options...');
+ const options = existingOptions && 'subitems' in existingOptions ? existingOptions.subitems : [];
+ options.push({ description: 'Create a Copy', event: this.onCopy, icon: 'copy' });
+ !existingOptions && ContextMenu.Instance.addItem({ description: 'Options...', subitems: options, icon: 'hand-point-right' });
+ };
renderFunctionInputs() {
- const descriptionInput =
- <textarea
- className="scriptingBox-textarea-inputs"
- onChange={e => this.functionDescription = e.target.value}
- placeholder="enter description here"
- value={this.functionDescription}
- />;
- const nameInput =
- <textarea
- className="scriptingBox-textarea-inputs"
- onChange={e => this.functionName = e.target.value}
- placeholder="enter name here"
- value={this.functionName}
- />;
-
- return <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()} >
- <div className="scriptingBox-wrapper" style={{ maxWidth: "100%" }}>
- <div className="container" style={{ maxWidth: "100%" }}>
- <div className="descriptor" style={{ textAlign: "center", display: "inline-block", maxWidth: "100%" }}> Enter a function name: </div>
- <div style={{ maxWidth: "100%" }}> {nameInput}</div>
- <div className="descriptor" style={{ textAlign: "center", display: "inline-block", maxWidth: "100%" }}> Enter a function description: </div>
- <div style={{ maxWidth: "100%" }}>{descriptionInput}</div>
+ const descriptionInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionDescription = e.target.value)} placeholder="enter description here" value={this.functionDescription} />;
+ const nameInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionName = e.target.value)} placeholder="enter name here" value={this.functionName} />;
+
+ return (
+ <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()}>
+ <div className="scriptingBox-wrapper" style={{ maxWidth: '100%' }}>
+ <div className="container" style={{ maxWidth: '100%' }}>
+ <div className="descriptor" style={{ textAlign: 'center', display: 'inline-block', maxWidth: '100%' }}>
+ {' '}
+ Enter a function name:{' '}
+ </div>
+ <div style={{ maxWidth: '100%' }}> {nameInput}</div>
+ <div className="descriptor" style={{ textAlign: 'center', display: 'inline-block', maxWidth: '100%' }}>
+ {' '}
+ Enter a function description:{' '}
+ </div>
+ <div style={{ maxWidth: '100%' }}>{descriptionInput}</div>
+ </div>
</div>
+ {this.renderErrorMessage()}
</div>
- {this.renderErrorMessage()}
- </div>;
+ );
}
renderErrorMessage() {
- return !this._errorMessage ? (null) : <div className="scriptingBox-errorMessage"> {this._errorMessage} </div>;
+ return !this._errorMessage ? null : <div className="scriptingBox-errorMessage"> {this._errorMessage} </div>;
}
// rendering when a doc's value can be set in applied UI
renderDoc(parameter: string) {
- return <div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()}
- ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))} >
- <EditableView display={"block"} maxHeight={72} height={35} fontSize={14}
- contents={this.dataDoc[parameter]?.title ?? "undefined"}
- GetValue={() => this.dataDoc[parameter]?.title ?? "undefined"}
- SetValue={action((value: string) => {
- const script = CompileScript(value, {
- addReturn: true,
- typecheck: false,
- transformer: DocumentIconContainer.getTransformer()
- });
- const results = script.compiled && script.run();
- if (results && results.success) {
- this._errorMessage = "";
- this.dataDoc[parameter] = results.result;
- return true;
- }
- this._errorMessage = "invalid document";
- return false;
- })}
- />
- </div>;
+ return (
+ <div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()} ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))}>
+ <EditableView
+ display={'block'}
+ maxHeight={72}
+ height={35}
+ fontSize={14}
+ contents={StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')}
+ GetValue={() => StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')}
+ SetValue={action((value: string) => {
+ const script = CompileScript(value, {
+ addReturn: true,
+ typecheck: false,
+ transformer: DocumentIconContainer.getTransformer(),
+ });
+ const results = script.compiled && script.run();
+ if (results && results.success) {
+ this._errorMessage = '';
+ Doc.SetInPlace(this.rootDoc, parameter, results.result, true);
+ return true;
+ }
+ this._errorMessage = 'invalid document';
+ return false;
+ })}
+ />
+ </div>
+ );
}
// rendering when a string's value can be set in applied UI
renderBasicType(parameter: string, isNum: boolean) {
- const strVal = (isNum ? NumCast(this.dataDoc[parameter]).toString() : StrCast(this.dataDoc[parameter]));
- return <div className="scriptingBox-paramInputs" style={{ overflowY: "hidden" }}>
- <EditableView display={"block"} maxHeight={72} height={35} fontSize={14}
- contents={strVal ?? "undefined"}
- GetValue={() => strVal ?? "undefined"}
- SetValue={action((value: string) => {
- const setValue = isNum ? parseInt(value) : value;
- if (setValue !== undefined && setValue !== " ") {
- this._errorMessage = "";
- this.dataDoc[parameter] = setValue;
- return true;
- }
- this._errorMessage = "invalid input";
- return false;
- })}
- />
- </div>;
+ const strVal = isNum ? NumCast(this.rootDoc[parameter]).toString() : StrCast(this.rootDoc[parameter]);
+ return (
+ <div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}>
+ <EditableView
+ display={'block'}
+ maxHeight={72}
+ height={35}
+ fontSize={14}
+ contents={strVal ?? 'undefined'}
+ GetValue={() => strVal ?? 'undefined'}
+ SetValue={action((value: string) => {
+ const setValue = isNum ? parseInt(value) : value;
+ if (setValue !== undefined && setValue !== ' ') {
+ this._errorMessage = '';
+ Doc.SetInPlace(this.rootDoc, parameter, setValue, true);
+ return true;
+ }
+ this._errorMessage = 'invalid input';
+ return false;
+ })}
+ />
+ </div>
+ );
}
// rendering when an enum's value can be set in applied UI (drop down box)
renderEnum(parameter: string, types: (string | boolean | number)[]) {
- return <div className="scriptingBox-paramInputs">
- <div className="scriptingBox-viewBase">
- <div className="commandEntry-outerDiv">
- <select className="scriptingBox-viewPicker"
- onPointerDown={e => e.stopPropagation()}
- onChange={e => this.viewChanged(e, parameter)}
- value={typeof (this.dataDoc[parameter]) === "string" ? "S" + StrCast(this.dataDoc[parameter]) :
- typeof (this.dataDoc[parameter]) === "number" ? "N" + NumCast(this.dataDoc[parameter]) :
- "B" + BoolCast(this.dataDoc[parameter])}>
- {types.map(type =>
- <option className="scriptingBox-viewOption" value={(typeof (type) === "string" ? "S" : typeof (type) === "number" ? "N" : "B") + type}> {type.toString()} </option>
- )}
- </select>
+ return (
+ <div className="scriptingBox-paramInputs">
+ <div className="scriptingBox-viewBase">
+ <div className="commandEntry-outerDiv">
+ <select
+ className="scriptingBox-viewPicker"
+ onPointerDown={e => e.stopPropagation()}
+ onChange={e => this.viewChanged(e, parameter)}
+ value={typeof this.rootDoc[parameter] === 'string' ? 'S' + StrCast(this.rootDoc[parameter]) : typeof this.rootDoc[parameter] === 'number' ? 'N' + NumCast(this.rootDoc[parameter]) : 'B' + BoolCast(this.rootDoc[parameter])}>
+ {types.map(type => (
+ <option className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
+ {' '}
+ {type.toString()}{' '}
+ </option>
+ ))}
+ </select>
+ </div>
</div>
</div>
- </div>;
+ );
}
// setting a parameter (checking type and name before it is added)
compileParam(value: string, whichParam?: number) {
- if (value.includes(":")) {
- const ptype = value.split(":")[1].trim();
- const pname = value.split(":")[0].trim();
- if (ptype === "Doc" || ptype === "string" || ptype === "number" || ptype === "boolean" || ptype.split("|")[1]) {
+ if (value.includes(':')) {
+ const ptype = value.split(':')[1].trim();
+ const pname = value.split(':')[0].trim();
+ if (ptype === 'Doc' || ptype === 'string' || ptype === 'number' || ptype === 'boolean' || ptype.split('|')[1]) {
if ((whichParam !== undefined && pname === this.paramsNames[whichParam]) || !this.paramsNames.includes(pname)) {
- this._errorMessage = "";
+ this._errorMessage = '';
if (whichParam !== undefined) {
this.compileParams[whichParam] = value;
} else {
- this.compileParams = [...value.split(";").filter(s => s), ...this.compileParams];
+ this.compileParams = [...value.split(';').filter(s => s), ...this.compileParams];
}
return true;
}
- this._errorMessage = "this name has already been used";
+ this._errorMessage = 'this name has already been used';
} else {
- this._errorMessage = "this type is not supported";
+ this._errorMessage = 'this type is not supported';
}
} else {
- this._errorMessage = "must set type of parameter";
+ this._errorMessage = 'must set type of parameter';
}
return false;
}
@@ -403,47 +444,46 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
this._suggestions.push(StrCast(element));
}
});
- return (this._suggestions);
+ return this._suggestions;
}
@action
handleFunc(pos: number) {
- const scriptString = this.rawScript.slice(0, pos - 2);
- this._currWord = scriptString.split(" ")[scriptString.split(" ").length - 1];
+ const scriptString = this.rawText.slice(0, pos - 2);
+ this._currWord = scriptString.split(' ')[scriptString.split(' ').length - 1];
this._suggestions = [StrCast(this._scriptingParams[this._currWord])];
- return (this._suggestions);
+ return this._suggestions;
}
-
getDescription(value: string) {
const descrip = this._scriptingDescriptions[value];
- return descrip?.length > 0 ? descrip : "";
+ return descrip?.length > 0 ? descrip : '';
}
getParams(value: string) {
const params = this._scriptingParams[value];
- return params?.length > 0 ? params : "";
+ return params?.length > 0 ? params : '';
}
returnParam(item: string) {
- const params = item.split(",");
- let value = "";
+ const params = item.split(',');
+ let value = '';
let first = true;
- params.forEach((element) => {
+ params.forEach(element => {
if (first) {
- value = element.split(":")[0].trim();
+ value = element.split(':')[0].trim();
first = false;
} else {
- value = value + ", " + element.split(":")[0].trim();
+ value = value + ', ' + element.split(':')[0].trim();
}
});
return value;
}
getSuggestedParams(pos: number) {
- const firstScript = this.rawScript.slice(0, pos);
- const indexP = firstScript.lastIndexOf(".");
- const indexS = firstScript.lastIndexOf(" ");
+ const firstScript = this.rawText.slice(0, pos);
+ const indexP = firstScript.lastIndexOf('.');
+ const indexS = firstScript.lastIndexOf(' ');
const func = firstScript.slice((indexP > indexS ? indexP : indexS) + 1, firstScript.length + 1);
return this._scriptingParams[func];
}
@@ -452,63 +492,64 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
suggestionPos = () => {
const getCaretCoordinates = require('textarea-caret');
const This = this;
- document.querySelector('textarea')?.addEventListener("input", function () {
+ document.querySelector('textarea')?.addEventListener('input', function () {
const caret = getCaretCoordinates(this, this.selectionEnd);
This._selection = this;
This.resetSuggestionPos(caret);
});
- }
+ };
@action
keyHandler(e: any, pos: number) {
- if (this._lastChar === "Enter") {
- this.rawScript = this.rawScript + " ";
+ e.stopPropagation();
+ if (this._lastChar === 'Enter') {
+ this.rawText = this.rawText + ' ';
}
- if (e.key === "(") {
+ if (e.key === '(') {
this.suggestionPos();
this._scriptParamsText = this.getSuggestedParams(pos);
this._scriptSuggestedParams = this.getSuggestedParams(pos);
if (this._scriptParamsText !== undefined && this._scriptParamsText.length > 0) {
- if (this.rawScript[pos - 2] !== "(") {
+ if (this.rawText[pos - 2] !== '(') {
this._paramSuggestion = true;
}
}
- } else if (e.key === ")") {
+ } else if (e.key === ')') {
this._paramSuggestion = false;
} else {
- if (e.key === "Backspace") {
- if (this._lastChar === "(") {
+ if (e.key === 'Backspace') {
+ if (this._lastChar === '(') {
this._paramSuggestion = false;
- } else if (this._lastChar === ")") {
- if (this.rawScript.slice(0, this.rawScript.length - 1).split("(").length - 1 > this.rawScript.slice(0, this.rawScript.length - 1).split(")").length - 1) {
+ } else if (this._lastChar === ')') {
+ if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
if (this._scriptParamsText.length > 0) {
this._paramSuggestion = true;
}
}
}
- } else if (this.rawScript.split("(").length - 1 <= this.rawScript.split(")").length - 1) {
+ } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
this._paramSuggestion = false;
}
}
- this._lastChar = e.key === "Backspace" ? this.rawScript[this.rawScript.length - 2] : e.key;
+ this._lastChar = e.key === 'Backspace' ? this.rawText[this.rawText.length - 2] : e.key;
if (this._paramSuggestion) {
- const parameters = this._scriptParamsText.split(",");
- const index = this.rawScript.lastIndexOf("(");
- const enteredParams = this.rawScript.slice(index, this.rawScript.length);
- const splitEntered = enteredParams.split(",");
+ const parameters = this._scriptParamsText.split(',');
+ const index = this.rawText.lastIndexOf('(');
+ const enteredParams = this.rawText.slice(index, this.rawText.length);
+ const splitEntered = enteredParams.split(',');
const numEntered = splitEntered.length;
parameters.forEach((element: string, i: number) => {
if (i !== parameters.length - 1) {
- parameters[i] = element + ",";
+ parameters[i] = element + ',';
}
});
- let first = "";
- let last = "";
+ let first = '';
+ let last = '';
parameters.forEach((element: string, i: number) => {
if (i < numEntered - 1) {
@@ -518,7 +559,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
}
});
- this._scriptSuggestedParams = <div> {first} <b>{parameters[numEntered - 1]}</b> {last} </div>;
+ this._scriptSuggestedParams = (
+ <div>
+ {' '}
+ {first} <b>{parameters[numEntered - 1]}</b> {last}{' '}
+ </div>
+ );
}
}
@@ -526,61 +572,73 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
handlePosChange(number: any) {
this._caretPos = number;
if (this._caretPos === 0) {
- this.rawScript = " " + this.rawScript;
+ this.rawText = ' ' + this.rawText;
} else if (this._spaced) {
this._spaced = false;
- if (this.rawScript[this._caretPos - 1] === " ") {
- this.rawScript = this.rawScript.slice(0, this._caretPos - 1) +
- this.rawScript.slice(this._caretPos, this.rawScript.length);
+ if (this.rawText[this._caretPos - 1] === ' ') {
+ this.rawText = this.rawText.slice(0, this._caretPos - 1) + this.rawText.slice(this._caretPos, this.rawText.length);
}
}
}
+ @observable rawText: string = '';
@computed({ keepAlive: true }) get renderScriptingBox() {
TraceMobx();
- return <div style={{ width: this.compileParams.length > 0 ? "70%" : "100%" }} ref={this._scriptTextRef}>
- <ReactTextareaAutocomplete className="ScriptingBox-textarea-script"
- minChar={1}
- placeholder="write your script here"
- onFocus={this.onFocus}
- onBlur={() => this._overlayDisposer?.()}
- onChange={e => this.rawScript = e.target.value}
- value={this.rawScript}
- movePopupAsYouType={true}
- loadingComponent={() => <span>Loading</span>}
-
- trigger={{
- " ": {
- dataProvider: (token: any) => this.handleToken(token),
- component: ({ entity: value }) => this.renderFuncListElement(value),
- output: (item: any, trigger) => {
- this._spaced = true;
- return trigger + item.trim();
+ return (
+ <div style={{ width: this.compileParams.length > 0 ? '70%' : '100%' }} ref={this._scriptTextRef}>
+ <ReactTextareaAutocomplete
+ className="ScriptingBox-textarea-script"
+ minChar={1}
+ placeholder="write your script here"
+ onFocus={this.onFocus}
+ onBlur={() => this._overlayDisposer?.()}
+ onChange={action((e: any) => (this.rawText = e.target.value))}
+ value={this.rawText}
+ movePopupAsYouType={true}
+ loadingComponent={() => <span>Loading</span>}
+ trigger={{
+ ' ': {
+ dataProvider: (token: any) => this.handleToken(token),
+ component: (blob: any) => {
+ console.log('Blob', blob);
+ return this.renderFuncListElement(blob.entity);
+ },
+ output: (item: any, trigger: any) => {
+ this._spaced = true;
+ return trigger + item.trim();
+ },
},
- },
- ".": {
- dataProvider: (token: any) => this.handleToken(token),
- component: ({ entity: value }) => this.renderFuncListElement(value),
- output: (item: any, trigger) => {
- this._spaced = true;
- return trigger + item.trim();
+ '.': {
+ dataProvider: (token: any) => this.handleToken(token),
+ component: (blob: any) => {
+ console.log('Blob', blob);
+ return this.renderFuncListElement(blob.entity);
+ },
+ output: (item: any, trigger: any) => {
+ this._spaced = true;
+ return trigger + item.trim();
+ },
},
- }
- }}
- onKeyDown={(e) => this.keyHandler(e, this._caretPos)}
- onCaretPositionChange={(number: any) => this.handlePosChange(number)}
- />
- </div>;
+ }}
+ onKeyDown={(e: React.KeyboardEvent) => this.keyHandler(e, this._caretPos)}
+ onCaretPositionChange={(number: any) => this.handlePosChange(number)}
+ />
+ </div>
+ );
}
renderFuncListElement(value: string | object) {
- return (typeof value !== "string") ? (null) : <div>
- <div style={{ fontSize: "14px" }}>
- {value}
+ return typeof value !== 'string' ? null : (
+ <div>
+ <div style={{ fontSize: '14px' }}>{value}</div>
+ <div key="desc" style={{ fontSize: '10px' }}>
+ {this.getDescription(value)}
+ </div>
+ <div key="params" style={{ fontSize: '10px' }}>
+ {this.getParams(value)}
+ </div>
</div>
- <div key="desc" style={{ fontSize: "10px" }}>{this.getDescription(value)}</div>
- <div key="params" style={{ fontSize: "10px" }}>{this.getParams(value)}</div>
- </div>;
+ );
}
// inputs for scripting div (script box, params box, and params column)
@@ -589,102 +647,184 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// should there be a border? style={{ borderStyle: "groove", borderBlockWidth: "1px" }}
// params box on bottom
- const parameterInput = <div className="scriptingBox-params">
- <EditableView display={"block"} maxHeight={72} height={35} fontSize={22}
- contents={""}
- GetValue={returnEmptyString}
- SetValue={value => value && value !== " " ? this.compileParam(value) : false}
- placeholder={"enter parameters here"}
- />
- </div>;
+ const parameterInput = (
+ <div className="scriptingBox-params">
+ <EditableView
+ display={'block'}
+ maxHeight={72}
+ height={35}
+ fontSize={22}
+ contents={''}
+ GetValue={returnEmptyString}
+ SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)}
+ placeholder={'enter parameters here'}
+ />
+ </div>
+ );
// params column on right side (list)
- const definedParameters = !this.compileParams.length ? (null) :
- <div className="scriptingBox-plist" style={{ width: "30%" }}>
- {this.compileParams.map((parameter, i) =>
- <div className="scriptingBox-pborder" onKeyPress={e => e.key === "Enter" && this._overlayDisposer?.()} >
- <EditableView display={"block"} maxHeight={72} height={35} fontSize={12} background-color={"beige"}
+ const definedParameters = !this.compileParams.length ? null : (
+ <div className="scriptingBox-plist" style={{ width: '30%' }}>
+ {this.compileParams.map((parameter, i) => (
+ <div className="scriptingBox-pborder" onKeyPress={e => e.key === 'Enter' && this._overlayDisposer?.()}>
+ <EditableView
+ display={'block'}
+ maxHeight={72}
+ height={35}
+ fontSize={12}
+ background-color={'beige'}
contents={parameter}
GetValue={() => parameter}
- SetValue={value => value && value !== " " ? this.compileParam(value, i) : this.onDelete(i)}
+ SetValue={value => (value && value !== ' ' ? this.compileParam(value, i) : this.onDelete(i))}
/>
</div>
- )}
- </div>;
+ ))}
+ </div>
+ );
- return <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()} >
- <div className="scriptingBox-wrapper">
- {this.renderScriptingBox}
- {definedParameters}
+ return (
+ <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()}>
+ <div className="scriptingBox-wrapper">
+ {this.renderScriptingBox}
+ {definedParameters}
+ </div>
+ {parameterInput}
+ {this.renderErrorMessage()}
</div>
- {parameterInput}
- {this.renderErrorMessage()}
- </div>;
+ );
}
// toolbar (with compile and apply buttons) for scripting UI
renderScriptingTools() {
- const buttonStyle = "scriptingBox-button" + (StrCast(this.rootDoc.layoutKey).startsWith("layout_on") ? "-finish" : "");
- return <div className="scriptingBox-toolbar">
- <button className={buttonStyle} onPointerDown={e => { this.onCompile(); e.stopPropagation(); }}>Compile</button>
- <button className={buttonStyle} onPointerDown={e => { this.onApply(); e.stopPropagation(); }}>Apply</button>
- <button className={buttonStyle} onPointerDown={e => { this.onSave(); e.stopPropagation(); }}>Save</button>
-
- {!StrCast(this.rootDoc.layoutKey).startsWith("layout_on") ? (null) : // onClick, onChecked, etc need a Finish button to return to their default layout
- <button className={buttonStyle} onPointerDown={e => { this.onFinish(); e.stopPropagation(); }}>Finish</button>}
- </div>;
+ const buttonStyle = 'scriptingBox-button' + (StrCast(this.rootDoc.layoutKey).startsWith('layout_on') ? '-finish' : '');
+ return (
+ <div className="scriptingBox-toolbar">
+ <button
+ className={buttonStyle}
+ onPointerDown={e => {
+ this.onCompile();
+ e.stopPropagation();
+ }}>
+ Compile
+ </button>
+ <button
+ className={buttonStyle}
+ onPointerDown={e => {
+ this.onApply();
+ e.stopPropagation();
+ }}>
+ Apply
+ </button>
+ <button
+ className={buttonStyle}
+ onPointerDown={e => {
+ this.onSave();
+ e.stopPropagation();
+ }}>
+ Save
+ </button>
+
+ {!StrCast(this.rootDoc.layoutKey).startsWith('layout_on') ? null : ( // onClick, onChecked, etc need a Finish button to return to their default layout
+ <button
+ className={buttonStyle}
+ onPointerDown={e => {
+ this.onFinish();
+ e.stopPropagation();
+ }}>
+ Finish
+ </button>
+ )}
+ </div>
+ );
}
// inputs UI for params which allows you to set values for each displayed in a list
renderParamsInputs() {
- return <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected(true) && e.stopPropagation()} >
- {!this.compileParams.length || !this.paramsNames ? (null) :
- <div className="scriptingBox-plist">
- {this.paramsNames.map((parameter: string, i: number) =>
- <div className="scriptingBox-pborder" onKeyPress={e => e.key === "Enter" && this._overlayDisposer?.()} >
- <div className="scriptingBox-wrapper" style={{ maxHeight: "40px" }}>
- <div className="scriptingBox-paramNames" > {`${parameter}:${this.paramsTypes[i]} = `} </div>
- {this.paramsTypes[i] === "boolean" ? this.renderEnum(parameter, [true, false]) : (null)}
- {this.paramsTypes[i] === "string" ? this.renderBasicType(parameter, false) : (null)}
- {this.paramsTypes[i] === "number" ? this.renderBasicType(parameter, true) : (null)}
- {this.paramsTypes[i] === "Doc" ? this.renderDoc(parameter) : (null)}
- {this.paramsTypes[i]?.split("|")[1] ? this.renderEnum(parameter, this.paramsTypes[i].split("|").map(s => !isNaN(parseInt(s.trim())) ? parseInt(s.trim()) : s.trim())) : (null)}
+ return (
+ <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected(true) && e.stopPropagation()}>
+ {!this.compileParams.length || !this.paramsNames ? null : (
+ <div className="scriptingBox-plist">
+ {this.paramsNames.map((parameter: string, i: number) => (
+ <div className="scriptingBox-pborder" onKeyPress={e => e.key === 'Enter' && this._overlayDisposer?.()}>
+ <div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
+ <div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
+ {this.paramsTypes[i] === 'boolean' ? this.renderEnum(parameter, [true, false]) : null}
+ {this.paramsTypes[i] === 'string' ? this.renderBasicType(parameter, false) : null}
+ {this.paramsTypes[i] === 'number' ? this.renderBasicType(parameter, true) : null}
+ {this.paramsTypes[i] === 'Doc' ? this.renderDoc(parameter) : null}
+ {this.paramsTypes[i]?.split('|')[1]
+ ? this.renderEnum(
+ parameter,
+ this.paramsTypes[i].split('|').map(s => (!isNaN(parseInt(s.trim())) ? parseInt(s.trim()) : s.trim()))
+ )
+ : null}
+ </div>
</div>
- </div>)}
- </div>}
- {this.renderErrorMessage()}
- </div>;
+ ))}
+ </div>
+ )}
+ {this.renderErrorMessage()}
+ </div>
+ );
}
// toolbar (with edit and run buttons and error message) for params UI
renderTools(toolSet: string, func: () => void) {
- const buttonStyle = "scriptingBox-button" + (StrCast(this.rootDoc.layoutKey).startsWith("layout_on") ? "-finish" : "");
- return <div className="scriptingBox-toolbar">
- <button className={buttonStyle} onPointerDown={e => { this.onEdit(); e.stopPropagation(); }}>Edit</button>
- <button className={buttonStyle} onPointerDown={e => { func(); e.stopPropagation(); }}>{toolSet}</button>
- {!StrCast(this.rootDoc.layoutKey).startsWith("layout_on") ? (null) :
- <button className={buttonStyle} onPointerDown={e => { this.onFinish(); e.stopPropagation(); }}>Finish</button>}
- </div>;
+ const buttonStyle = 'scriptingBox-button' + (StrCast(this.rootDoc.layoutKey).startsWith('layout_on') ? '-finish' : '');
+ return (
+ <div className="scriptingBox-toolbar">
+ <button
+ className={buttonStyle}
+ onPointerDown={e => {
+ this.onEdit();
+ e.stopPropagation();
+ }}>
+ Edit
+ </button>
+ <button
+ className={buttonStyle}
+ onPointerDown={e => {
+ func();
+ e.stopPropagation();
+ }}>
+ {toolSet}
+ </button>
+ {!StrCast(this.rootDoc.layoutKey).startsWith('layout_on') ? null : (
+ <button
+ className={buttonStyle}
+ onPointerDown={e => {
+ this.onFinish();
+ e.stopPropagation();
+ }}>
+ Finish
+ </button>
+ )}
+ </div>
+ );
}
// renders script UI if _applied = false and params UI if _applied = true
render() {
+ console.log(ReactTextareaAutocomplete);
TraceMobx();
return (
- <div className={`scriptingBox`} onContextMenu={this.specificContextMenu}
- onPointerUp={!this._function ? this.suggestionPos : undefined}>
- <div className="scriptingBox-outerDiv"
- onWheel={e => this.props.isSelected(true) && e.stopPropagation()}>
- {this._paramSuggestion ? <div className="boxed" ref={this._suggestionRef} style={{ left: this._suggestionBoxX + 20, top: this._suggestionBoxY - 15, display: "inline" }}> {this._scriptSuggestedParams} </div> : null}
+ <div className={`scriptingBox`} onContextMenu={this.specificContextMenu} onPointerUp={!this._function ? this.suggestionPos : undefined}>
+ <div className="scriptingBox-outerDiv" onWheel={e => this.props.isSelected(true) && e.stopPropagation()}>
+ {this._paramSuggestion ? (
+ <div className="boxed" ref={this._suggestionRef} style={{ left: this._suggestionBoxX + 20, top: this._suggestionBoxY - 15, display: 'inline' }}>
+ {' '}
+ {this._scriptSuggestedParams}{' '}
+ </div>
+ ) : null}
{!this._applied && !this._function ? this.renderScriptingInputs : null}
{this._applied && !this._function ? this.renderParamsInputs() : null}
{!this._applied && this._function ? this.renderFunctionInputs() : null}
{!this._applied && !this._function ? this.renderScriptingTools() : null}
- {this._applied && !this._function ? this.renderTools("Run", () => this.onRun()) : null}
- {!this._applied && this._function ? this.renderTools("Create Function", () => this.onCreate()) : null}
+ {this._applied && !this._function ? this.renderTools('Run', () => this.onRun()) : null}
+ {!this._applied && this._function ? this.renderTools('Create Function', () => this.onCreate()) : null}
</div>
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f0d7bd2f3..aa51714da 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,6 @@
-.mini-viewer{
+@import "../global/globalCssVariables.scss";
+
+.mini-viewer {
cursor: grab;
position: absolute;
right: 10;
@@ -13,23 +15,24 @@
width: 100%;
height: 100%;
position: relative;
+
.videoBox-viewer {
- display:flex;
+ display: flex;
flex-direction: column;
height: 100%;
border-radius: inherit;
- opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
+ opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
+ background: $dark-gray;
}
+
.inkingCanvas-paths-markers {
- opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
- }
- .collectionStackedTimeline {
- background: beige;
+ opacity: 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
}
+
.videoBox-stackPanel {
z-index: -1;
width: 100%;
- position: relative;
+ position: relative;
}
.videoBox-annotationLayer {
@@ -43,26 +46,33 @@
}
}
-.videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
-.videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen {
+.videoBox-content-YouTube,
+.videoBox-content-YouTube-fullScreen,
+.videoBox-content,
+.videoBox-content-interactive,
+.videoBox-cont-fullScreen {
width: 100%;
z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt
position: absolute;
+
video {
width: auto;
- height: 100%;
- display: flex;
+ height: 100%;
+ display: flex;
margin: auto;
}
}
-.videoBox-content, .videoBox-content-interactive, .videoBox-content-fullScreen {
+.videoBox-content,
+.videoBox-content-interactive,
+.videoBox-content-fullScreen {
width: 100%;
- height: 100%;
+ height: 100%;
left: 0px;
}
-.videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen {
+.videoBox-content-YouTube,
+.videoBox-content-YouTube-fullScreen {
height: 100%;
}
@@ -70,28 +80,127 @@
// pointer-events: all;
// }
+.videoBox-ui-wrapper {
+ width: 0;
+ height: 0;
+}
+
.videoBox-ui {
position: absolute;
flex-direction: row;
- right: 5px;
- top: 5px;
- display: none;
- background-color: rgba(0, 0, 0, 0.6);
+ align-items: center;
+ justify-content: center;
+ display: flex;
+ background-color: $dark-gray;
+ color: white;
+ border-radius: 100px;
+ height: 40px;
+ padding: 0 10px 0 7px;
+ transition: opacity 0.3s;
+ z-index: 100001;
+
+ .timecode-controls {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ margin: 0 2px;
+ flex-grow: 2;
+ font-size: 14px;
+
+ .timeline-slider {
+ margin: 5px;
+ flex-grow: 2;
+ }
+ }
+
+ .toolbar-slider.volume, .toolbar-slider.zoom {
+ width: 50px;
+ }
+
+ .videobox-button {
+ margin: 2px;
+ cursor: pointer;
+ width: 25px;
+ height: 25px;
+ border-radius: 50%;
+ background: $dark-gray;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background: $black;
+ }
+
+ svg {
+ width: 15px;
+ height: 15px;
+ }
+ }
}
-.videoBox-time, .videoBox-snapshot, .videoBox-timelineButton, .videoBox-play, .videoBox-full {
- color : white;
+
+.videoBox-time,
+.videoBox-snapshot,
+.videoBox-timelineButton,
+.videoBox-play,
+.videoBox-full {
+ color: white;
position: relative;
transform-origin: left top;
- pointer-events:all;
+ pointer-events: all;
padding-right: 5px;
cursor: pointer;
+
&:hover {
- background-color: gray;
+ background-color: $medium-gray;
}
}
-.videoBox:hover {
+.videoBox-content-fullScreen, .videoBox-content-fullScreen-interactive {
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+
.videoBox-ui {
- display: flex;
+ left: 50%;
+ top: 90%;
+ transform: translate(-50%, -50%);
+ width: 80%;
+ transition: top 0s, width 0s, opacity 0.3s, visibility 0.3s;
}
+}
+
+video::-webkit-media-controls {
+ display: none !important;
+}
+
+input[type="range"] {
+ -webkit-appearance: none;
+ background: none;
+}
+
+input[type="range"]:focus {
+ outline: none;
+}
+
+input[type="range"]::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 10px;
+ cursor: pointer;
+ box-shadow: 0;
+ background: $light-gray;
+ border-radius: 10px;
+}
+
+input[type="range"]::-webkit-slider-thumb {
+ box-shadow: 0;
+ border: 0;
+ height: 12px;
+ width: 12px;
+ border-radius: 10px;
+ background: $medium-blue;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -1px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 33fdc4935..a3d501153 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,114 +1,249 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from "mobx";
-import { observer } from "mobx-react";
-import { basename } from "path";
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
+import { observer } from 'mobx-react';
+import { basename } from 'path';
import * as rp from 'request-promise';
-import { Doc, DocListCast } from "../../../fields/Doc";
-import { InkTool } from "../../../fields/InkField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { AudioField, VideoField } from "../../../fields/URLField";
-import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { Networking } from "../../Network";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
-import { DocumentDecorations } from "../DocumentDecorations";
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
-import { AnchorMenu } from "../pdf/AnchorMenu";
-import { StyleProp } from "../StyleProvider";
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
+import { InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
+import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Networking } from '../../Network';
+import { DocumentManager } from '../../util/DocumentManager';
+import { ReplayMovements } from '../../util/ReplayMovements';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { undoBatch } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { DocumentDecorations } from '../DocumentDecorations';
+import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { AnchorMenu } from '../pdf/AnchorMenu';
+import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
-import { LinkDocPreview } from "./LinkDocPreview";
-import "./VideoBox.scss";
+import { RecordingBox } from './RecordingBox';
+import './VideoBox.scss';
+const path = require('path');
+
+/**
+ * VideoBox
+ * Main component: VideoBox.tsx
+ * Supporting Components: CollectionStackedTimeline
+ *
+ * VideoBox is a node that supports the playback of video files in Dash.
+ * When a video file or YouTube video is importeed into Dash, it is immediately rendered as a VideoBox document.
+ * CollectionStackedTimline handles AudioBox and VideoBox shared behavior, but VideoBox handles playing, pausing, etc because it contains <video> element
+ * User can trim video: nondestructive, just sets new bounds for playback and rendering timeline
+ * Like images, users can zoom and pan and it has an overlay layer allowing for annotations on top of the video at different times
+ */
@observer
export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(VideoBox, fieldKey);
+ }
+ /**
+ * Uploads an image buffer to the server and stores with specified filename. by default the image
+ * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large)
+ * @param imageUri the bytes of the image
+ * @param returnedFilename the base filename to store the image on the server
+ * @param nosuffix optionally suppress creating multiple resolution images
+ */
+ public static async convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) {
+ try {
+ const posting = Utils.prepend('/uploadURI');
+ const returnedUri = await rp.post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename,
+ nosuffix,
+ replaceRootFilename,
+ },
+ json: true,
+ });
+ return returnedUri;
+ } catch (e) {
+ console.log('VideoBox :' + e);
+ }
+ }
+
static _youtubeIframeCounter: number = 0;
- static Instance: VideoBox;
- static heightPercent = 60; // height of timeline in percent of height of videoBox.
+ static heightPercent = 80; // height of video relative to videoBox when timeline is open
+ static numThumbnails = 20;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _youtubePlayer: YT.Player | undefined = undefined;
- private _videoRef: HTMLVideoElement | null = null;
+ private _videoRef: HTMLVideoElement | null = null; // <video> ref
+ private _contentRef: HTMLDivElement | null = null; // ref to div that wraps video and controls for full screen
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
- private _stackedTimeline = React.createRef<CollectionStackedTimeline>();
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _audioPlayer: HTMLAudioElement | null = null;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); // outermost div
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- private _playRegionTimer: any = null;
- private _playRegionDuration = 0;
- @observable static _nativeControls: boolean;
- @observable _marqueeing: number[] | undefined;
+ private _playRegionTimer: any = null; // timeout for playback
+ private _controlsFadeTimer: any = null; // timeout for controls fade
+
+ @observable _stackedTimeline: any; // CollectionStackedTimeline ref
+ @observable static _nativeControls: boolean; // default html controls
+ @observable _marqueeing: number[] | undefined; // coords for marquee selection
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _screenCapture = false;
- @observable _clicking = false;
+ @observable _clicking = false; // used for transition between showing/hiding timeline
@observable _forceCreateYouTubeIFrame = false;
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
@observable _playing = false;
- @computed get links() { return DocListCast(this.dataDoc.links); }
- @computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); }
- @computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+ @observable _finished: boolean = false; // has playback reached end of clip
+ @observable _volume: number = 1;
+ @observable _muted: boolean = false;
+ @observable _controlsTransform?: { X: number; Y: number };
+ @observable _controlsVisible: boolean = true;
+ @observable _scrubbing: boolean = false;
- private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; }
- public get player(): HTMLVideoElement | null { return this._videoRef; }
+ @computed get links() {
+ return DocListCast(this.dataDoc.links);
+ }
+ @computed get heightPercent() {
+ return NumCast(this.layoutDoc._timelineHeightPercent, 100);
+ } // current percent of video relative to VideoBox height
+ // @computed get rawDuration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+ @observable rawDuration: number = 0;
- constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) {
- super(props);
- VideoBox.Instance = this;
+ @computed get youtubeVideoId() {
+ const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ return field && field.url.href.indexOf('youtube') !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split('/')) : '';
}
- getAnchor = () => {
- const timecode = Cast(this.layoutDoc._currentTimecode, "number", null);
- const marquee = AnchorMenu.Instance.GetAnchor?.();
- return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined, undefined, marquee) || this.rootDoc;
+ // returns the path of the audio file
+ @computed get audiopath() {
+ const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null);
+ const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
+ return field?.url.href ?? vfield?.url.href ?? '';
}
- videoLoad = () => {
- const aspect = this.player!.videoWidth / this.player!.videoHeight;
- Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth);
- Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight);
- this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
- if (Number.isFinite(this.player!.duration)) {
- this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration;
+ // returns the presentation data if it exists, null otherwise
+ @computed get presentation() {
+ const data = this.dataDoc[this.fieldKey + '-presentation'];
+ return data ? JSON.parse(data) : null;
+ }
+
+ @computed private get timeline() {
+ return this._stackedTimeline;
+ }
+ private get transition() {
+ return this._clicking ? 'left 0.5s, width 0.5s, height 0.5s' : '';
+ } // css transition for hiding/showing timeline
+ public get player(): HTMLVideoElement | null {
+ return this._videoRef;
+ }
+
+ componentDidMount() {
+ this.props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+ if (this.youtubeVideoId) {
+ const youtubeaspect = 400 / 315;
+ const nativeWidth = Doc.NativeWidth(this.layoutDoc);
+ const nativeHeight = Doc.NativeHeight(this.layoutDoc);
+ if (!nativeWidth || !nativeHeight) {
+ if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 600);
+ Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 600) / youtubeaspect);
+ this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
+ }
+ }
+ this.player && this.setPlayheadTime(0);
+ document.addEventListener('keydown', this.keyEvents, true);
+
+ if (this.presentation) {
+ ReplayMovements.Instance.setVideoBox(this);
+ }
+ }
+
+ componentWillUnmount() {
+ this.removeCurrentlyPlaying();
+ this.Pause();
+ Object.keys(this._disposers).forEach(d => this._disposers[d]?.());
+ document.removeEventListener('keydown', this.keyEvents, true);
+
+ if (this.presentation) {
+ ReplayMovements.Instance.removeVideoBox();
}
}
+ // handles key events, when timeline scrubs fade controls
+ @action
+ keyEvents = (e: KeyboardEvent) => {
+ if (
+ // need to include range inputs because after dragging time slider it becomes target element
+ !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
+ this.props.isSelected(true)
+ ) {
+ switch (e.key) {
+ case 'ArrowLeft':
+ case 'ArrowRight':
+ clearTimeout(this._controlsFadeTimer);
+ this._scrubbing = true;
+ this._controlsFadeTimer = setTimeout(
+ action(() => (this._scrubbing = false)),
+ 500
+ );
+ e.stopPropagation();
+ break;
+ }
+ }
+ };
+
+ // plays video
@action public Play = (update: boolean = true) => {
+ if (this._playRegionTimer) return;
+
this._playing = true;
- try {
- this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime);
- update && this.player?.play();
- update && this._audioPlayer?.play();
- update && this._youtubePlayer?.playVideo();
- this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
- } catch (e) {
- console.log("Video Play Exception:", e);
+ const eleTime = this.player?.currentTime || 0;
+ if (this.timeline) {
+ let start = eleTime >= this.timeline.trimEnd || eleTime <= this.timeline.trimStart ? this.timeline.trimStart : eleTime;
+
+ if (this._finished) {
+ // restarts video if reached end on previous play
+ this._finished = false;
+ start = this.timeline.trimStart;
+ }
+
+ try {
+ this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime);
+ update && this.player && this.playFrom(start, undefined, true);
+ update && this._audioPlayer?.play();
+ update && this._youtubePlayer?.playVideo();
+ this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
+ } catch (e) {
+ console.log('Video Play Exception:', e);
+ }
}
this.updateTimecode();
- }
+ };
+ // goes to time
@action public Seek(time: number) {
try {
this._youtubePlayer?.seekTo(Math.round(time), true);
} catch (e) {
- console.log("Video Seek Exception:", e);
+ console.log('Video Seek Exception:', e);
}
this.player && (this.player.currentTime = time);
this._audioPlayer && (this._audioPlayer.currentTime = time);
+ // TODO: revisit this and clean it
+ if ((this.player?.currentTime || -1) < this.rawDuration) {
+ this._finished = false;
+ }
}
+ // pauses video
@action public Pause = (update: boolean = true) => {
this._playing = false;
+ this.removeCurrentlyPlaying();
try {
update && this.player?.pause();
update && this._audioPlayer?.pause();
@@ -116,47 +251,93 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
this._youtubePlayer?.seekTo(this._youtubePlayer?.getCurrentTime(), true);
} catch (e) {
- console.log("Video Pause Exception:", e);
+ console.log('Video Pause Exception:', e);
}
this._youtubePlayer && SelectionManager.DeselectAll(); // if we don't deselect the player, then we get an annoying YouTube spinner I guess telling us we're paused.
this._playTimer = undefined;
- this.props.renderDepth !== -1 && this.updateTimecode();
- }
+ this.updateTimecode();
+ if (!this._finished) {
+ clearTimeout(this._playRegionTimer); // if paused in the middle of playback, prevents restart on next play
+ }
+ this._playRegionTimer = undefined;
+ };
+ // toggles video full screen
@action public FullScreen = () => {
- this._fullScreen = true;
- this.player && this.player.requestFullscreen();
+ if (document.fullscreenElement === this._contentRef) {
+ this._fullScreen = false;
+ this.player && this._contentRef && document.exitFullscreen();
+ } else {
+ this._fullScreen = true;
+ this.player && this._contentRef && this._contentRef.requestFullscreen();
+ }
try {
- this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add");
+ this._youtubePlayer && this.props.addDocTab(this.rootDoc, 'add');
} catch (e) {
- console.log("Video FullScreen Exception:", e);
+ console.log('Video FullScreen Exception:', e);
}
- }
+ };
- @action public Snapshot(downX?: number, downY?: number) {
+ // fades out controls in fullscreen after mouse stops moving
+ @action controlsFade = (e: PointerEvent) => {
+ e.stopPropagation();
+ if (!this._scrubbing) {
+ clearTimeout(this._controlsFadeTimer);
+ this._controlsVisible = true;
+ this._controlsFadeTimer = setTimeout(
+ action(() => (this._controlsVisible = false)),
+ 3000
+ );
+ }
+ };
+
+ // drag controls around window in fulls screen
+ @action controlsDrag = (e: React.PointerEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const eleStyle = getComputedStyle(e.target as Element);
+ this._controlsTransform = { X: parseInt(eleStyle.left), Y: parseInt(eleStyle.top) };
+
+ setupMoveUpEvents(
+ e.target,
+ e,
+ action((e, down, delta) => {
+ if (this._controlsTransform) {
+ this._controlsTransform.X = Math.max(0, Math.min(delta[0] + this._controlsTransform.X, window.innerWidth));
+ this._controlsTransform.Y = Math.max(0, Math.min(delta[1] + this._controlsTransform.Y, window.innerHeight));
+ }
+ return false;
+ }),
+ emptyFunction,
+ emptyFunction
+ );
+ };
+
+ // creates and links snapshot photo of current video frame
+ @action public Snapshot = (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => {
const width = NumCast(this.layoutDoc._width);
const canvas = document.createElement('canvas');
canvas.width = 640;
- canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1);
- const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+ canvas.height = (640 * Doc.NativeHeight(this.layoutDoc)) / (Doc.NativeWidth(this.layoutDoc) || 1);
+ const ctx = canvas.getContext('2d'); //draw image to canvas. scale to target dimensions
if (ctx) {
- // ctx.rect(0, 0, canvas.width, canvas.height);
- // ctx.fillStyle = "blue";
- // ctx.fill();
this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
}
if (!this._videoRef) {
const b = Docs.Create.LabelDocument({
- x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y, 1),
- _width: 150, _height: 50, title: (this.layoutDoc._currentTimecode || 0).toString(),
- _isLinkButton: true
+ x: NumCast(this.layoutDoc.x) + width,
+ y: NumCast(this.layoutDoc.y, 1),
+ _width: 150,
+ _height: 50,
+ title: (this.layoutDoc._currentTimecode || 0).toString(),
+ _isLinkButton: true,
});
this.props.addDocument?.(b);
- DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, "video snapshot");
- Networking.PostToServer("/youtubeScreenshot", {
+ DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, 'video snapshot');
+ Networking.PostToServer('/youtubeScreenshot', {
id: this.youtubeVideoId,
- timecode: this.layoutDoc._currentTimecode
+ timecode: this.layoutDoc._currentTimecode,
}).then(response => {
const resolved = response?.accessPaths?.agnostic?.client;
if (resolved) {
@@ -168,469 +349,805 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
//convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- const retitled = StrCast(this.rootDoc.title).replace(/[ -\.]/g, "");
- const encodedFilename = encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_"));
+ const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, '');
+ const encodedFilename = encodeURIComponent('snapshot' + retitled + '_' + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, '_'));
const filename = basename(encodedFilename);
- VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) =>
- returnedFilename && this.createRealSummaryLink(returnedFilename, downX, downY));
+ VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createRealSummaryLink)(returnedFilename, downX, downY));
}
- }
+ };
+
+ updateIcon = () => {
+ const makeIcon = (returnedfilename: string) => {
+ this.dataDoc.icon = new ImageField(returnedfilename);
+ this.dataDoc['icon-nativeWidth'] = this.layoutDoc[WidthSym]();
+ this.dataDoc['icon-nativeHeight'] = this.layoutDoc[HeightSym]();
+ };
+ this.Snapshot(undefined, undefined, makeIcon);
+ };
- private createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => {
- const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath;
+ // creates link for snapshot
+ createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => {
+ const url = !imagePath.startsWith('/') ? Utils.CorsProxy(imagePath) : imagePath;
const width = NumCast(this.layoutDoc._width) || 1;
const height = NumCast(this.layoutDoc._height);
const imageSummary = Docs.Create.ImageDocument(url, {
- _nativeWidth: Doc.NativeWidth(this.layoutDoc), _nativeHeight: Doc.NativeHeight(this.layoutDoc),
- x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y), _isLinkButton: true,
- _width: 150, _height: height / width * 150, title: "--snapshot" + NumCast(this.layoutDoc._currentTimecode) + " image-"
+ _nativeWidth: Doc.NativeWidth(this.layoutDoc),
+ _nativeHeight: Doc.NativeHeight(this.layoutDoc),
+ x: NumCast(this.layoutDoc.x) + width,
+ y: NumCast(this.layoutDoc.y),
+ _isLinkButton: true,
+ _width: 150,
+ _height: (height / width) * 150,
+ title: '--snapshot' + NumCast(this.layoutDoc._currentTimecode) + ' image-',
});
Doc.SetNativeWidth(Doc.GetProto(imageSummary), Doc.NativeWidth(this.layoutDoc));
Doc.SetNativeHeight(Doc.GetProto(imageSummary), Doc.NativeHeight(this.layoutDoc));
this.props.addDocument?.(imageSummary);
- const link = DocUtils.MakeLink({ doc: imageSummary }, { doc: this.getAnchor() }, "video snapshot");
+ const link = DocUtils.MakeLink({ doc: imageSummary }, { doc: this.getAnchor() }, 'video snapshot');
link && (Doc.GetProto(link.anchor2 as Doc).timecodeToHide = NumCast((link.anchor2 as Doc).timecodeToShow) + 3);
- setTimeout(() =>
- (downX !== undefined && downY !== undefined) && DocumentManager.Instance.getFirstDocumentView(imageSummary)?.startDragging(downX, downY, "move", true));
- }
+ setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSummary)?.startDragging(downX, downY, 'move', true));
+ };
+
+ getAnchor = () => {
+ const timecode = Cast(this.layoutDoc._currentTimecode, 'number', null);
+ const marquee = AnchorMenu.Instance.GetAnchor?.();
+ return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow' /* videoStart */, '_timecodeToHide' /* videoEnd */, timecode ? timecode : undefined, undefined, marquee) || this.rootDoc;
+ };
+ // sets video info on load
+ videoLoad = action(() => {
+ const aspect = this.player!.videoWidth / this.player!.videoHeight;
+ Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth);
+ Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight);
+ this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
+ if (Number.isFinite(this.player!.duration)) {
+ this.rawDuration = this.player!.duration;
+ } else this.rawDuration = NumCast(this.dataDoc[this.fieldKey + '-duration']);
+ });
+
+ // updates video time
@action
updateTimecode = () => {
this.player && (this.layoutDoc._currentTimecode = this.player.currentTime);
try {
this._youtubePlayer && (this.layoutDoc._currentTimecode = this._youtubePlayer.getCurrentTime?.());
} catch (e) {
- console.log("Video Timecode Exception:", e);
+ console.log('Video Timecode Exception:', e);
}
- }
+ };
- componentDidMount() {
- this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
- this._disposers.triggerVideo = reaction(
- () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
- time => time !== undefined && setTimeout(() => {
- this.player && this.Play();
- setTimeout(() => this.Document._triggerVideo = undefined, 10);
- }, this.player ? 0 : 250), // wait for mainCont and try again to play
- { fireImmediately: true }
- );
- this._disposers.triggerStop = reaction(
- () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? NumCast(this.Document._triggerVideoStop, null) : undefined,
- stop => stop !== undefined && setTimeout(() => {
- this.player && this.Pause();
- setTimeout(() => this.Document._triggerVideoStop = undefined, 10);
- }, this.player ? 0 : 250), // wait for mainCont and try again to play
- { fireImmediately: true }
- );
- if (this.youtubeVideoId) {
- const youtubeaspect = 400 / 315;
- const nativeWidth = Doc.NativeWidth(this.layoutDoc);
- const nativeHeight = Doc.NativeHeight(this.layoutDoc);
- if (!nativeWidth || !nativeHeight) {
- if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 600);
- Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 600) / youtubeaspect);
- this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
+ // extracts video thumbnails and saves them as field of doc
+ getVideoThumbnails = () => {
+ if (this.dataDoc.thumbnails !== undefined) return;
+ this.dataDoc.thumbnails = new List<string>();
+ const thumbnailPromises: Promise<any>[] = [];
+ const video = document.createElement('video');
+
+ video.onloadedmetadata = () => (video.currentTime = 0);
+
+ video.onseeked = () => {
+ const canvas = document.createElement('canvas');
+ canvas.height = 100;
+ canvas.width = 100;
+ canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
+ const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, '');
+ const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
+ thumbnailPromises?.push(VideoBox.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
+ const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
+ if (newTime < video.duration) {
+ video.currentTime = newTime;
+ } else {
+ Promise.all(thumbnailPromises).then(thumbnails => (this.dataDoc.thumbnails = new List<string>(thumbnails)));
}
- }
- }
+ };
- componentWillUnmount() {
- this.Pause();
- Object.keys(this._disposers).forEach(d => this._disposers[d]?.());
- }
+ const field = Cast(this.dataDoc[this.fieldKey], VideoField);
+ field && (video.src = field.url.href);
+ };
+ // sets video element ref
@action
setVideoRef = (vref: HTMLVideoElement | null) => {
this._videoRef = vref;
if (vref) {
this._videoRef!.ontimeupdate = this.updateTimecode;
// @ts-ignore
- vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
+ // vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
this._disposers.reactionDisposer?.();
- this._disposers.reactionDisposer = reaction(() => NumCast(this.layoutDoc._currentTimecode),
- time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
+ this._disposers.reactionDisposer = reaction(
+ () => NumCast(this.layoutDoc._currentTimecode),
+ time => !this._playing && (vref.currentTime = time),
+ { fireImmediately: true }
+ );
+
+ (!this.dataDoc.thumbnails || this.dataDoc.thumbnails.length != VideoBox.numThumbnails) && this.getVideoThumbnails();
}
- }
+ };
- public static async convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false) {
- try {
- const posting = Utils.prepend("/uploadURI");
- const returnedUri = await rp.post(posting, {
- body: {
- uri: imageUri,
- name: returnedFilename,
- nosuffix
- },
- json: true,
+ // set ref for div that wraps video and controls for fullscreen
+ @action
+ setContentRef = (cref: HTMLDivElement | null) => {
+ this._contentRef = cref;
+ if (cref) {
+ cref.onfullscreenchange = action(e => {
+ this._fullScreen = document.fullscreenElement === cref;
+ this._controlsVisible = true;
+ this._scrubbing = false;
+ clearTimeout(this._controlsFadeTimer);
+ if (this._fullScreen) {
+ document.addEventListener('pointermove', this.controlsFade);
+ } else {
+ document.removeEventListener('pointermove', this.controlsFade);
+ }
});
- return returnedUri;
-
- } catch (e) {
- console.log("VideoBox :" + e);
}
- }
+ };
+ // context menu
specificContextMenu = (e: React.MouseEvent): void => {
const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
if (field) {
const url = field.url.href;
const subitems: ContextMenuProps[] = [];
- subitems.push({ description: "Full Screen", event: this.FullScreen, icon: "expand" });
- subitems.push({ description: "Take Snapshot", event: this.Snapshot, icon: "expand-arrows-alt" });
- this.rootDoc.type === DocumentType.SCREENSHOT && subitems.push({
- description: "Screen Capture", event: (async () => {
- runInAction(() => this._screenCapture = !this._screenCapture);
- this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
- }), icon: "expand-arrows-alt"
+ subitems.push({ description: 'Full Screen', event: this.FullScreen, icon: 'expand' });
+ subitems.push({ description: 'Take Snapshot', event: this.Snapshot, icon: 'expand-arrows-alt' });
+ this.rootDoc.type === DocumentType.SCREENSHOT &&
+ subitems.push({
+ description: 'Screen Capture',
+ event: async () => {
+ runInAction(() => (this._screenCapture = !this._screenCapture));
+ this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
+ },
+ icon: 'expand-arrows-alt',
+ });
+ subitems.push({ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', event: () => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), icon: 'expand-arrows-alt' });
+ subitems.push({
+ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
+ event: () => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+ icon: 'expand-arrows-alt',
+ });
+ subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' });
+ // subitems.push({ description: "Toggle Native Controls", event: action(() => VideoBox._nativeControls = !VideoBox._nativeControls), icon: "expand-arrows-alt" });
+ // subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" });
+ // subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" });
+ // subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" });
+ subitems.push({
+ description: 'Copy path',
+ event: () => {
+ Utils.CopyText(url);
+ },
+ icon: 'expand-arrows-alt',
});
- subitems.push({ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") + " play when link is selected", event: () => this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks, icon: "expand-arrows-alt" });
- subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors, icon: "expand-arrows-alt" });
- subitems.push({ description: "Toggle Native Controls", event: action(() => VideoBox._nativeControls = !VideoBox._nativeControls), icon: "expand-arrows-alt" });
- subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
+ // if the videobox was turned from a recording box
+ if (this.dataDoc[this.fieldKey + '-recorded'] === true) {
+ subitems.push({
+ description: 'Recreate recording',
+ event: () => {
+ this.dataDoc.layout = RecordingBox.LayoutString(this.fieldKey);
+ // delete assoicated video data
+ this.dataDoc[this.props.fieldKey] = '';
+ this.dataDoc[this.fieldKey + '-duration'] = '';
+ // delete assoicated presentation data
+ this.dataDoc[this.fieldKey + '-presentation'] = '';
+ },
+ icon: 'expand-arrows-alt',
+ });
+ }
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems: subitems, icon: 'video' });
}
- }
+ };
- // returns the path of the audio file
- @computed get audiopath() {
- const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null);
- const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
- return field?.url.href ?? vfield?.url.href ?? "";
- }
// ref for updating time
- _audioPlayer: HTMLAudioElement | null = null;
- setAudioRef = (e: HTMLAudioElement | null) => this._audioPlayer = e;
+ setAudioRef = (e: HTMLAudioElement | null) => (this._audioPlayer = e);
+
+ // renders the video and audio
@computed get content() {
const field = Cast(this.dataDoc[this.fieldKey], VideoField);
- const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
- const classname = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
- return !field ? <div key="loading">Loading</div> :
- <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: "multiply" }}>
- <div className={classname}>
- <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+ const interactive = Doc.ActiveTool !== InkTool.None || !this.props.isSelected() ? '' : '-interactive';
+ const classname = 'videoBox-content' + (this._fullScreen ? '-fullScreen' : '') + interactive;
+ const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0;
+ return !field ? (
+ <div key="loading">Loading</div>
+ ) : (
+ <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: 'multiply', cursor: this._fullScreen && !this._controlsVisible ? 'none' : 'pointer' }}>
+ <div className={classname} ref={this.setContentRef} onPointerDown={e => this._fullScreen && e.stopPropagation()}>
+ {this._fullScreen && (
+ <div
+ className="videoBox-ui"
+ onPointerDown={this.controlsDrag}
+ style={{
+ left: this._controlsTransform && this._controlsTransform.X,
+ top: this._controlsTransform && this._controlsTransform.Y,
+ visibility: this._controlsVisible || this._scrubbing ? 'visible' : 'hidden',
+ opacity: opacity,
+ }}>
+ {this.UIButtons}
+ </div>
+ )}
+ <video
+ key="video"
+ autoPlay={this._screenCapture}
+ ref={this.setVideoRef}
+ style={this._fullScreen ? this.fullScreenSize() : {}}
onCanPlay={this.videoLoad}
controls={VideoBox._nativeControls}
onPlay={() => this.Play()}
onSeeked={this.updateTimecode}
onPause={() => this.Pause()}
- onClick={e => e.preventDefault()}>
+ onClick={this._fullScreen ? () => (this.playing() ? this.Pause() : this.Play()) : e => e.preventDefault()}>
<source src={field.url.href} type="video/mp4" />
Not supported.
</video>
- {!this.audiopath || this.audiopath === field.url.href ? (null) :
- <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}>
+ {!this.audiopath || this.audiopath === field.url.href ? null : (
+ <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}>
<source src={this.audiopath} type="audio/mpeg" />
Not supported.
- </audio>}
+ </audio>
+ )}
</div>
- </div>;
- }
-
- @computed get youtubeVideoId() {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
- return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : "";
+ </div>
+ );
}
@action youtubeIframeLoaded = (e: any) => {
if (!this._youtubeContentCreated) {
this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame;
return;
- }
- else this._youtubeContentCreated = false;
+ } else this._youtubeContentCreated = false;
this.loadYouTube(e.target);
- }
- private loadYouTube = (iframe: any) => {
+ };
+
+ loadYouTube = (iframe: any) => {
let started = true;
- const onYoutubePlayerStateChange = (event: any) => runInAction(() => {
- if (started && event.data === YT.PlayerState.PLAYING) {
- started = false;
- this._youtubePlayer?.unMute();
- //this.Pause();
- return;
- }
- if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
- if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
- });
+ const onYoutubePlayerStateChange = (event: any) =>
+ runInAction(() => {
+ if (started && event.data === YT.PlayerState.PLAYING) {
+ started = false;
+ this._youtubePlayer?.unMute();
+ //this.Pause();
+ return;
+ }
+ if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
+ if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
+ });
const onYoutubePlayerReady = (event: any) => {
this._disposers.reactionDisposer?.();
this._disposers.youtubeReactionDisposer?.();
- this._disposers.reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek(NumCast(this.layoutDoc._currentTimecode)));
+ this._disposers.reactionDisposer = reaction(
+ () => this.layoutDoc._currentTimecode,
+ () => !this._playing && this.Seek(NumCast(this.layoutDoc._currentTimecode))
+ );
this._disposers.youtubeReactionDisposer = reaction(
- () => CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
- (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true });
+ () => Doc.ActiveTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
+ interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
+ { fireImmediately: true }
+ );
};
- if (typeof (YT) === undefined) setTimeout(() => this.loadYouTube(iframe), 100);
+ if (typeof YT === undefined) setTimeout(() => this.loadYouTube(iframe), 100);
else {
(YT as any)?.ready(() => {
this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
events: {
- 'onReady': this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
- 'onStateChange': this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
- }
+ onReady: this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
+ onStateChange: this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
+ },
});
});
}
- }
- private get uIButtons() {
- const curTime = NumCast(this.layoutDoc._currentTimecode);
- const nonNativeControls = [
- <Tooltip title={<div className="dash-tooltip">{"playback"}</div>} key="play" placement="bottom">
- <div className="videoBox-play" onPointerDown={this.onPlayDown} >
- <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
- </div>
- </Tooltip>,
- <Tooltip title={<div className="dash-tooltip">{"timecode"}</div>} key="time" placement="bottom">
- <div className="videoBox-time" onPointerDown={this.onResetDown} >
- <span>{formatTime(curTime)}</span>
- <span style={{ fontSize: 8 }}>{" " + Math.floor((curTime - Math.trunc(curTime)) * 100).toString().padStart(2, "0")}</span>
- </div>
- </Tooltip>,
- <Tooltip title={<div className="dash-tooltip">{"view full screen"}</div>} key="full" placement="bottom">
- <div className="videoBox-full" onPointerDown={this.FullScreen}>
- <FontAwesomeIcon icon="expand" size="lg" />
- </div>
- </Tooltip>];
- return <div className="videoBox-ui">
- {[...(VideoBox._nativeControls ? [] : nonNativeControls),
- <Tooltip title={<div className="dash-tooltip">{"snapshot current frame"}</div>} key="snap" placement="bottom">
- <div className="videoBox-snapshot" onPointerDown={this.onSnapshotDown} >
- <FontAwesomeIcon icon="camera" size="lg" />
- </div>
- </Tooltip>,
- <Tooltip title={<div className="dash-tooltip">{"show annotation timeline"}</div>} key="timeline" placement="bottom">
- <div className="videoBox-timelineButton" onPointerDown={this.onTimelineHdlDown}>
- <FontAwesomeIcon icon="eye" size="lg" />
- </div>
- </Tooltip>,]}
- </div>;
- }
+ };
- onPlayDown = () => this._playing ? this.Pause() : this.Play();
+ // for play button
+ onPlayDown = () => (this._playing ? this.Pause() : this.Play());
+ // for fullscreen button
onFullDown = (e: React.PointerEvent) => {
this.FullScreen();
e.stopPropagation();
e.preventDefault();
- }
+ };
+ // for snapshot button
onSnapshotDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, (e) => {
- this.Snapshot(e.clientX, e.clientY);
- return true;
- }, emptyFunction, () => this.Snapshot());
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ e => {
+ this.Snapshot(e.clientX, e.clientY);
+ return true;
+ },
+ emptyFunction,
+ () => this.Snapshot()
+ );
+ };
- onTimelineHdlDown = action((e: React.PointerEvent) => {
+ // for show/hide timeline button, transitions between show/hide
+ @action
+ onTimelineHdlDown = (e: React.PointerEvent) => {
this._clicking = true;
- setupMoveUpEvents(this, e,
- action((e: PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ action(encodeURIComponent => {
this._clicking = false;
if (this.props.isContentActive()) {
- const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
- this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
+ // const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
+ // this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
+
+ this.layoutDoc._timelineHeightPercent = 80;
}
return false;
- }), emptyFunction,
+ }),
+ emptyFunction,
() => {
this.layoutDoc._timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent;
- setTimeout(action(() => this._clicking = false), 500);
- }, this.props.isContentActive(), this.props.isContentActive());
- });
-
- onResetDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e,
- (e: PointerEvent) => {
- this.Seek(Math.max(0, NumCast(this.layoutDoc._currentTimecode) + Math.sign(e.movementX) * 0.0333));
- e.stopImmediatePropagation();
- return false;
+ setTimeout(
+ action(() => (this._clicking = false)),
+ 500
+ );
},
- emptyFunction,
- (e: PointerEvent) => this.layoutDoc._currentTimecode = 0);
- }
+ this.props.isContentActive(),
+ this.props.isContentActive()
+ );
+ };
+
+ // removes video from currently playing display
+ @action
+ removeCurrentlyPlaying = () => {
+ if (CollectionStackedTimeline.CurrentlyPlaying) {
+ const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc);
+ index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
+ }
+ };
+
+ // adds video to currently playing display
+ @action
+ addCurrentlyPlaying = () => {
+ if (!CollectionStackedTimeline.CurrentlyPlaying) {
+ CollectionStackedTimeline.CurrentlyPlaying = [];
+ }
+ if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc) === -1) {
+ CollectionStackedTimeline.CurrentlyPlaying.push(this.layoutDoc);
+ }
+ };
@computed get youtubeContent() {
this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
- const classname = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
+ const classname = 'videoBox-content-YouTube' + (this._fullScreen ? '-fullScreen' : '');
const start = untracked(() => Math.round(NumCast(this.layoutDoc._currentTimecode)));
- return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
- onPointerLeave={this.updateTimecode}
- onLoad={this.youtubeIframeLoaded} className={classname} width={Doc.NativeWidth(this.layoutDoc) || 640} height={Doc.NativeHeight(this.layoutDoc) || 390}
- src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._nativeControls ? 1 : 0}`} />;
+ return (
+ <iframe
+ key={this._youtubeIframeId}
+ id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
+ onPointerLeave={this.updateTimecode}
+ onLoad={this.youtubeIframeLoaded}
+ className={classname}
+ width={Doc.NativeWidth(this.layoutDoc) || 640}
+ height={Doc.NativeHeight(this.layoutDoc) || 390}
+ src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._nativeControls ? 1 : 0}`}
+ />
+ );
}
+ // for annotating, adds doc with time info
@action.bound
addDocWithTimecode(doc: Doc | Doc[]): boolean {
const docs = doc instanceof Doc ? [doc] : doc;
const curTime = NumCast(this.layoutDoc._currentTimecode);
- docs.forEach(doc => doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1);
+ docs.forEach(doc => (doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1));
return this.addDocument(doc);
}
- // play back the video from time
+ // play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range
@action
- playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => {
+ playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
clearTimeout(this._playRegionTimer);
- this._playRegionDuration = endTime - seekTimeInSeconds;
+ this._playRegionTimer = undefined;
if (Number.isNaN(this.player?.duration)) {
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
} else if (this.player) {
- if (seekTimeInSeconds < 0) {
- if (seekTimeInSeconds > -1) {
- setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000);
- } else {
- this.Pause();
- }
- } else if (seekTimeInSeconds <= this.player.duration) {
- this.player.currentTime = seekTimeInSeconds;
+ // trimBounds override requested playback bounds
+ const end = Math.min(this.timeline?.trimEnd ?? this.rawDuration, endTime ?? this.timeline?.trimEnd ?? this.rawDuration);
+ const start = Math.max(this.timeline?.trimStart ?? 0, seekTimeInSeconds);
+ const playRegionDuration = end - start;
+ // checks if times are within clip range
+ if (seekTimeInSeconds >= 0 && (this.timeline?.trimStart || 0) <= end && seekTimeInSeconds <= (this.timeline?.trimEnd || this.rawDuration)) {
+ this.player.currentTime = start;
this._audioPlayer && (this._audioPlayer.currentTime = seekTimeInSeconds);
this.player.play();
this._audioPlayer?.play();
- runInAction(() => this._playing = true);
- if (endTime !== this.duration) {
- this._playRegionTimer = setTimeout(() => this.Pause(), (this._playRegionDuration) * 1000); // use setTimeout to play a specific duration
- }
+ this._playing = true;
+ this.addCurrentlyPlaying();
+ this._playRegionTimer = setTimeout(() => {
+ // need to keep track of if end of clip is reached so on next play, clip restarts
+ if (fullPlay) this._finished = true;
+ // removes from currently playing if playback has reached end of range marker
+ else this.removeCurrentlyPlaying();
+ this.Pause();
+ }, playRegionDuration * 1000);
} else {
this.Pause();
}
}
+ };
+
+ // ends trim, hides trim controls and displays new clip
+ @undoBatch
+ finishTrim = action(() => {
+ this.Pause();
+ this.setPlayheadTime(Math.max(Math.min(this.timeline?.trimEnd || 0, this.player!.currentTime), this.timeline?.trimStart || 0));
+ this.timeline?.StopTrimming();
+ });
+
+ // displays trim controls to start trimming clip
+ startTrim = (scope: TrimScope) => {
+ this.Pause();
+ this.timeline?.StartTrimming(scope);
+ };
+
+ // for trim button, double click displays full clip, single displays curr trim bounds
+ onClipPointerDown = (e: React.PointerEvent) => {
+ // if timeline isn't shown, show first then trim
+ this.heightPercent >= 100 && this.onTimelineHdlDown(e);
+ this.timeline &&
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ returnFalse,
+ action((e: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap) {
+ this.startTrim(TrimScope.All);
+ } else if (this.timeline) {
+ this.Pause();
+ this.timeline.IsTrimming !== TrimScope.None ? this.finishTrim() : this.startTrim(TrimScope.Clip);
+ }
+ })
+ );
+ };
+
+ // for volume slider sets volume
+ @action
+ setVolume = (volume: number) => {
+ if (this.player) {
+ this._volume = volume;
+ this.player.volume = volume;
+ if (this._muted) {
+ this.toggleMute();
+ }
+ }
+ };
+
+ // toggles video mute
+ @action
+ toggleMute = () => {
+ if (this.player) {
+ this._muted = !this._muted;
+ this.player.muted = this._muted;
+ }
+ };
+
+ // stretches vertically or horizontally depending on video orientation so video fits full screen
+ fullScreenSize() {
+ if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) {
+ return { height: '100%' };
+ } else {
+ return { width: '100%' };
+ }
}
+ // for zoom slider, sets timeline waveform zoom
+ zoom = (zoom: number) => {
+ this.timeline?.setZoom(zoom);
+ };
+
+ // plays link
playLink = (doc: Doc) => {
- const startTime = Math.max(0, (this._stackedTimeline.current?.anchorStart(doc) || 0));
- const endTime = this._stackedTimeline.current?.anchorEnd(doc);
+ const startTime = Math.max(0, this._stackedTimeline?.anchorStart(doc) || 0);
+ const endTime = this.timeline?.anchorEnd(doc);
if (startTime !== undefined) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
else this.Seek(startTime);
}
- }
-
- playing = () => this._playing;
- timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
- timelineScreenToLocal = () => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight());
- setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time;
- timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100;
- @computed get renderTimeline() {
- return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
- <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
- fieldKey={this.annotationKey}
- dictationKey={this.fieldKey + "-dictation"}
- mediaPath={this.audiopath}
- renderDepth={this.props.renderDepth + 1}
- startTag={"_timecodeToShow" /* videoStart */}
- endTag={"_timecodeToHide" /* videoEnd */}
- bringToFront={emptyFunction}
- CollectionView={undefined}
- duration={this.duration}
- playFrom={this.playFrom}
- setTime={this.setAnchorTime}
- playing={this.playing}
- isAnyChildContentActive={this.isAnyChildContentActive}
- whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- ScreenToLocalTransform={this.timelineScreenToLocal}
- Play={this.Play}
- Pause={this.Pause}
- playLink={this.playLink}
- PanelHeight={this.timelineHeight}
- trimming={false}
- trimStart={0}
- trimEnd={this.duration}
- trimDuration={this.duration}
- setStartTrim={emptyFunction}
- setEndTrim={emptyFunction}
- />
- </div>;
- }
-
- @computed get annotationLayer() {
- return <div className="videoBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
- }
+ };
+ // starts marquee selection
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
- setupMoveUpEvents(this, e, action(e => {
- MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeing = [e.clientX, e.clientY];
- return true;
- }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
+ if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }),
+ returnFalse,
+ () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
+ false
+ );
}
- }
+ };
- finishMarquee = action(() => {
+ // ends marquee selection
+ @action
+ finishMarquee = () => {
this._marqueeing = undefined;
this.props.select(true);
- });
+ };
+
+ timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+
+ timelineScreenToLocal = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .scale(this.scaling())
+ .translate(0, (-this.heightPercent / 100) * this.props.PanelHeight());
+
+ setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._currentTimecode = time);
+
+ timelineHeight = () => (this.props.PanelHeight() * (100 - this.heightPercent)) / 100;
+
+ playing = () => this._playing;
- @computed get fitWidth() { return this.props.docViewPath?.().slice(-1)[0].fitWidth; }
contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
- scaling = () => this.props.scaling?.() || 1;
- panelWidth = (): number => this.fitWidth ? this.props.PanelWidth() : (Doc.NativeAspect(this.rootDoc) || 1) * this.panelHeight();
- panelHeight = (): number => this.fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : this.heightPercent / 100 * this.props.PanelHeight();
+
+ scaling = () => this.props.NativeDimScaling?.() || 1;
+
+ panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
+ panelHeight = () => (this.layoutDoc._fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : (this.props.PanelHeight() * this.heightPercent) / 100);
+
screenToLocalTransform = () => {
const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
- return this.props.ScreenToLocalTransform().translate(-offset, 0).scale(100 / this.heightPercent);
- }
- marqueeFitScaling = () => (this.props.scaling?.() || 1) * this.heightPercent / 100;
- marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0];
+ return this.props
+ .ScreenToLocalTransform()
+ .translate(-offset, 0)
+ .scale(100 / this.heightPercent);
+ };
+
+ marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100;
+ marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
+
timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`];
+
+ // renders video controls
+ componentUI = (boundsLeft: number, boundsTop: number) => {
+ const bounds = this.props.docViewPath().lastElement().getBounds();
+ const left = bounds?.left || 0;
+ const right = bounds?.right || 0;
+ const top = bounds?.top || 0;
+ const height = (bounds?.bottom || 0) - top;
+ const width = Math.max(right - left, 100);
+ const uiHeight = Math.max(25, Math.min(50, height / 10));
+ const uiMargin = Math.min(10, height / 20);
+ const vidHeight = (height * this.heightPercent) / 100;
+ const yPos = top + vidHeight - uiHeight - uiMargin;
+ const xPos = uiHeight / vidHeight > 0.4 ? right + 10 : left + 10;
+ const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0;
+ return this._fullScreen || right - left < 50 ? null : (
+ <div className="videoBox-ui-wrapper" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
+ <div className="videoBox-ui" style={{ left: xPos, top: yPos, height: uiHeight, width: width - 20, transition: this._clicking ? 'top 0.5s' : '', opacity: opacity }}>
+ {this.UIButtons}
+ </div>
+ </div>
+ );
+ };
+
+ @computed get UIButtons() {
+ const bounds = this.props.docViewPath().lastElement().getBounds();
+ const width = (bounds?.right || 0) - (bounds?.left || 0);
+ const curTime = NumCast(this.layoutDoc._currentTimecode) - (this.timeline?.clipStart || 0);
+ return (
+ <>
+ <div className="videobox-button" title={this._playing ? 'play' : 'pause'} onPointerDown={this.onPlayDown}>
+ <FontAwesomeIcon icon={this._playing ? 'pause' : 'play'} />
+ </div>
+
+ {this.timeline && width > 150 && (
+ <div className="timecode-controls">
+ <div className="timecode-current">{formatTime(curTime)}</div>
+
+ {this._fullScreen || (this.heightPercent === 100 && width > 200) ? (
+ <div className="timeline-slider">
+ <input
+ type="range"
+ step="0.1"
+ min={this.timeline.clipStart}
+ max={this.timeline.clipEnd}
+ value={curTime}
+ className="toolbar-slider time-progress"
+ onPointerDown={action((e: React.PointerEvent) => {
+ e.stopPropagation();
+ this._scrubbing = true;
+ })}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setPlayheadTime(Number(e.target.value))}
+ onPointerUp={action((e: React.PointerEvent) => {
+ e.stopPropagation();
+ this._scrubbing = false;
+ })}
+ />
+ </div>
+ ) : (
+ <div>/</div>
+ )}
+
+ <div className="timecode-end">{formatTime(this.timeline.clipDuration)}</div>
+ </div>
+ )}
+
+ <div className="videobox-button" title={'full screen'} onPointerDown={this.onFullDown}>
+ <FontAwesomeIcon icon="expand" />
+ </div>
+
+ {!this._fullScreen && width > 300 && (
+ <div className="videobox-button" title={'show timeline'} onPointerDown={this.onTimelineHdlDown}>
+ <FontAwesomeIcon icon="eye" />
+ </div>
+ )}
+
+ {!this._fullScreen && width > 300 && (
+ <div className="videobox-button" title={this.timeline?.IsTrimming !== TrimScope.None ? 'finish trimming' : 'start trim'} onPointerDown={this.onClipPointerDown}>
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} />
+ </div>
+ )}
+
+ <div
+ className="videobox-button"
+ title={this._muted ? 'unmute' : 'mute'}
+ onPointerDown={e => {
+ e.stopPropagation();
+ this.toggleMute();
+ }}>
+ <FontAwesomeIcon icon={this._muted ? 'volume-mute' : 'volume-up'} />
+ </div>
+ {width > 300 && (
+ <input
+ type="range"
+ style={{ width: `min(25%, 50px)` }}
+ step="0.1"
+ min="0"
+ max="1"
+ value={this._muted ? 0 : this._volume}
+ className="toolbar-slider volume"
+ onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setVolume(Number(e.target.value))}
+ />
+ )}
+
+ {!this._fullScreen && this.heightPercent !== 100 && width > 300 && (
+ <>
+ <div className="videobox-button" title="zoom">
+ <FontAwesomeIcon icon="search-plus" />
+ </div>
+ <input
+ type="range"
+ step="0.1"
+ min="1"
+ max="5"
+ value={this.timeline?._zoomFactor}
+ className="toolbar-slider zoom"
+ onPointerDown={(e: React.PointerEvent) => {
+ e.stopPropagation();
+ }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ this.zoom(Number(e.target.value));
+ }}
+ />
+ </>
+ )}
+ </>
+ );
+ }
+
+ // renders CollectionStackedTimeline
+ @computed get renderTimeline() {
+ return (
+ <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
+ <CollectionStackedTimeline
+ ref={action((r: any) => (this._stackedTimeline = r))}
+ {...this.props}
+ fieldKey={this.annotationKey}
+ dictationKey={this.fieldKey + '-dictation'}
+ mediaPath={this.audiopath}
+ renderDepth={this.props.renderDepth + 1}
+ startTag={'_timecodeToShow' /* videoStart */}
+ endTag={'_timecodeToHide' /* videoEnd */}
+ bringToFront={emptyFunction}
+ CollectionView={undefined}
+ playFrom={this.playFrom}
+ setTime={this.setPlayheadTime}
+ playing={this.playing}
+ isAnyChildContentActive={this.isAnyChildContentActive}
+ whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}
+ removeDocument={this.removeDocument}
+ ScreenToLocalTransform={this.timelineScreenToLocal}
+ Play={this.Play}
+ Pause={this.Pause}
+ playLink={this.playLink}
+ PanelHeight={this.timelineHeight}
+ rawDuration={this.rawDuration}
+ />
+ </div>
+ );
+ }
+
+ // renders annotation layer
+ @computed get annotationLayer() {
+ return <div className="videoBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
+ }
+
+ savedAnnotations = () => this._savedAnnotations;
render() {
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
- const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
- return (<div className="videoBox" onContextMenu={this.specificContextMenu} ref={this._mainCont}
- style={{
- pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
- borderRadius,
- overflow: this.props.docViewPath?.().slice(-1)[0].fitWidth ? "auto" : undefined
- }} onWheel={e => { e.stopPropagation(); e.preventDefault(); }}>
- <div className="videoBox-viewer" onPointerDown={this.marqueeDown} >
- <div style={{
- position: "absolute", transition: this.transition,
- width: this.panelWidth(),
- height: this.panelHeight(),
- top: 0,
- left: (this.props.PanelWidth() - this.panelWidth()) / 2
+ const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / this.scaling()}px` : borderRad;
+ return (
+ <div
+ className="videoBox"
+ onContextMenu={this.specificContextMenu}
+ ref={this._mainCont}
+ style={{
+ pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
+ borderRadius,
+ overflow: this.props.docViewPath?.().slice(-1)[0].fitWidth ? 'auto' : undefined,
+ }}
+ onWheel={e => {
+ e.stopPropagation();
+ e.preventDefault();
}}>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- renderDepth={this.props.renderDepth + 1}
- fieldKey={this.annotationKey}
- CollectionView={undefined}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={true}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- ScreenToLocalTransform={this.screenToLocalTransform}
- docFilters={this.timelineDocFilter}
- select={emptyFunction}
- scaling={returnOne}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocWithTimecode}>
- {this.contentFunc}
- </CollectionFreeFormView>
+ <div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
+ <div
+ style={{
+ position: 'absolute',
+ transition: this.transition,
+ width: this.panelWidth(),
+ height: this.panelHeight(),
+ top: 0,
+ left: (this.props.PanelWidth() - this.panelWidth()) / 2,
+ }}>
+ <CollectionFreeFormView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ renderDepth={this.props.renderDepth + 1}
+ fieldKey={this.annotationKey}
+ CollectionView={undefined}
+ isAnnotationOverlay={true}
+ annotationLayerHostsContent={true}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ docFilters={this.timelineDocFilter}
+ select={emptyFunction}
+ NativeDimScaling={returnOne}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocWithTimecode}>
+ {this.contentFunc}
+ </CollectionFreeFormView>
+ </div>
+ {this.annotationLayer}
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ <MarqueeAnnotator
+ rootDoc={this.rootDoc}
+ scrollTop={0}
+ down={this._marqueeing}
+ scaling={this.marqueeFitScaling}
+ docView={this.props.docViewPath().slice(-1)[0]}
+ containerOffset={this.marqueeOffset}
+ addDocument={this.addDocWithTimecode}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ />
+ )}
+ {this.renderTimeline}
</div>
- {this.annotationLayer}
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator
- rootDoc={this.rootDoc}
- scrollTop={0}
- down={this._marqueeing}
- scaling={this.marqueeFitScaling}
- docView={this.props.docViewPath().slice(-1)[0]}
- containerOffset={this.marqueeOffset}
- addDocument={this.addDocWithTimecode}
- finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
- annotationLayer={this._annotationLayer.current}
- mainCont={this._mainCont.current}
- />}
- {this.renderTimeline}
- {this.uIButtons}
</div>
- </div >);
+ );
}
}
-VideoBox._nativeControls = false; \ No newline at end of file
+VideoBox._nativeControls = false;
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 7dc970496..d8dd074a5 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -5,11 +5,18 @@
height: 100%;
position: relative;
display: flex;
- .webBox-background {
+
+ .webBox-sideResizer {
+ position: absolute;
width: 100%;
height: 100%;
cursor: ew-resize;
- background: lightGray;
+ background: darkgray;
+ }
+
+ .webBox-background {
+ width: 100%;
+ height: 100%;
}
.webBox-ui {
@@ -114,7 +121,7 @@
box-shadow: $standard-box-shadow;
transition: 0.2s;
- &:hover{
+ &:hover {
filter: brightness(0.85);
}
}
@@ -149,11 +156,15 @@
position: absolute;
top: 0;
left: 0;
+ cursor: text;
+ padding: 15px;
+ height: 100%
}
.webBox-cont {
pointer-events: none;
}
+
.webBox-cont,
.webBox-cont-interactive {
padding: 0vw;
@@ -187,13 +198,14 @@
top: 0;
left: 0;
overflow: auto;
+
.webBox-innerContent {
position: relative;
}
}
div.webBox-outerContent::-webkit-scrollbar-thumb {
- display: none;
+ cursor: nw-resize;
}
}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7ff47107e..ca9f363c1 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,50 +1,48 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
import * as WebRequest from 'web-request';
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { HtmlField } from "../../../fields/HtmlField";
-import { InkTool } from "../../../fields/InkField";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, ImageCast, NumCast, StrCast } from "../../../fields/Types";
-import { ImageField, WebField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { KeyCodes } from "../../util/KeyCodes";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SnappingManager } from "../../util/SnappingManager";
-import { undoBatch } from "../../util/UndoManager";
-import { MarqueeOptionsMenu } from "../collections/collectionFreeForm";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
-import { DocumentDecorations } from "../DocumentDecorations";
-import { Colors } from "../global/globalEnums";
-import { LightboxView } from "../LightboxView";
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
-import { AnchorMenu } from "../pdf/AnchorMenu";
-import { Annotation } from "../pdf/Annotation";
-import { SidebarAnnos } from "../SidebarAnnos";
-import { StyleProp } from "../StyleProvider";
-import { DocumentViewProps } from "./DocumentView";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { HtmlField } from '../../../fields/HtmlField';
+import { InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageField, WebField } from '../../../fields/URLField';
+import { TraceMobx } from '../../../fields/util';
+import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SnappingManager } from '../../util/SnappingManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { DocumentDecorations } from '../DocumentDecorations';
+import { Colors } from '../global/globalEnums';
+import { LightboxView } from '../LightboxView';
+import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { AnchorMenu } from '../pdf/AnchorMenu';
+import { Annotation } from '../pdf/Annotation';
+import { SidebarAnnos } from '../SidebarAnnos';
+import { StyleProp } from '../StyleProvider';
+import { DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import { LinkDocPreview } from "./LinkDocPreview";
-import { VideoBox } from "./VideoBox";
-import "./WebBox.scss";
-import React = require("react");
-const { CreateImage } = require("./WebBoxRenderer");
-const _global = (window /* browser */ || global /* node */) as any;
-const htmlToText = require("html-to-text");
-
+import { LinkDocPreview } from './LinkDocPreview';
+import { VideoBox } from './VideoBox';
+import './WebBox.scss';
+import React = require('react');
+const { CreateImage } = require('./WebBoxRenderer');
+const _global = (window /* browser */ || global) /* node */ as any;
+const htmlToText = require('html-to-text');
@observer
export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(WebBox, fieldKey);
+ }
public static openSidebarWidth = 250;
public static sidebarResizerWidth = 5;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
@@ -53,12 +51,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _disposers: { [name: string]: IReactionDisposer } = {};
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _keyInput = React.createRef<HTMLInputElement>();
- private _initialScroll: Opt<number>;
+ private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop));
+ private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
private _searchRef = React.createRef<HTMLInputElement>();
- private _searchString = "";
- @observable private _webUrl = ""; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render.
- @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled
+ private _searchString = '';
+ @observable private _webUrl = ''; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render.
+ @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled
@observable private _searching: boolean = false;
@observable private _showSidebar = false;
@observable private _scrollTimer: any;
@@ -69,20 +68,42 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
@observable private _iframe: HTMLIFrameElement | null = null;
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight, 1500);
- @computed get _url() { return this.webField?.toString() || ""; }
- @computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; }
- @computed get scrollHeight() { return this._scrollHeight; }
- @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); }
- @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
- @computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; }
- @computed get webThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; }
+ @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
+ @computed get _url() {
+ return this.webField?.toString() || '';
+ }
+ @computed get _urlHash() {
+ return this._url ? WebBox.urlHash(this._url) + '' : '';
+ }
+ @computed get scrollHeight() {
+ return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight);
+ }
+ @computed get allAnnotations() {
+ return DocListCast(this.dataDoc[this.annotationKey]);
+ }
+ @computed get inlineTextAnnotations() {
+ return this.allAnnotations.filter(a => a.textInlineAnnotations);
+ }
+ @computed get webField() {
+ return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url;
+ }
+ @computed get webThumb() {
+ return (
+ this.props.thumbShown?.() &&
+ ImageCast(
+ this.layoutDoc['thumb-frozen'],
+ ImageCast(
+ this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop && this.layoutDoc.thumbNativeWidth === NumCast(this.layoutDoc.nativeWidth) && this.layoutDoc.thumbNativeHeight === NumCast(this.layoutDoc.nativeHeight)
+ ? this.layoutDoc.thumb
+ : undefined
+ )
+ )?.url
+ );
+ }
constructor(props: any) {
super(props);
- Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
- Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
- runInAction(() => this._webUrl = this._url); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
+ runInAction(() => (this._webUrl = this._url)); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
}
@action
@@ -102,68 +123,94 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
(this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true);
}
return true;
- }
+ };
+ @action
+ setScrollPos = (pos: number) => {
+ if (!this._outerRef.current || this._outerRef.current.scrollHeight < pos) {
+ if (this._webPageHasBeenRendered) setTimeout(() => this.setScrollPos(pos), 250);
+ } else {
+ this._outerRef.current.scrollTop = pos;
+ this._initialScroll = undefined;
+ }
+ };
+
+ updateThumb = async () => {
+ const imageBitmap = ImageCast(this.layoutDoc['thumb-frozen'])?.url.href;
+ const scrollTop = NumCast(this.layoutDoc._scrollTop);
+ const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+ const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth();
+ if (
+ !this.rootDoc.thumbLockout &&
+ !this.props.dontRegisterView &&
+ this._iframe &&
+ !imageBitmap &&
+ (scrollTop !== this.layoutDoc.thumbScrollTop || nativeWidth !== this.layoutDoc.thumbNativeWidth || nativeHeight !== this.layoutDoc.thumbNativeHeight)
+ ) {
+ var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+ if (!htmlString) {
+ htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
+ }
+ this.layoutDoc.thumb = undefined;
+ this.rootDoc.thumbLockout = true; // lock to prevent multiple thumb updates.
+ CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop)
+ .then((data_url: any) => {
+ VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
+ setTimeout(
+ action(() => {
+ this.rootDoc.thumbLockout = false;
+ this.layoutDoc.thumb = new ImageField(returnedfilename);
+ this.layoutDoc.thumbScrollTop = scrollTop;
+ this.layoutDoc.thumbNativeWidth = nativeWidth;
+ this.layoutDoc.thumbNativeHeight = nativeHeight;
+ }),
+ 500
+ )
+ );
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+ }
+ };
async componentDidMount() {
this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
runInAction(() => {
- this._annotationKeySuffix = () => this._urlHash + "-annotations";
+ this._annotationKeySuffix = () => this._urlHash + '-annotations';
+ const reqdFuncs: { [key: string]: string } = {};
// bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created)
- this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`);
- this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`);
+ reqdFuncs[this.fieldKey + '-annotations'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`;
+ reqdFuncs[this.fieldKey + '-sidebar'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`;
+ DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs);
});
- reaction(() => this.props.isSelected(),
- async (selected) => {
+ reaction(
+ () => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document),
+ async selected => {
if (selected) {
this._webPageHasBeenRendered = true;
- setTimeout(action(() => {
- this._scrollHeight = Math.max(this.scrollHeight, this._iframe?.contentDocument?.body.scrollHeight || 0);
- if (this._initialScroll !== undefined && this._outerRef.current) {
- setTimeout(() => {
- this._outerRef.current!.scrollTop = this._initialScroll!;
- this._initialScroll = undefined;
- });
- }
- }));
- } else if (!this.props.isContentActive() &&
- !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && /// don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
- LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
- const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
- if (this._iframe && !imageBitmap) {
- var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
- if (!htmlString) {
- htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
- }
- this.layoutDoc.thumb = undefined;
- const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
- CreateImage(
- this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl,
- this._iframe.contentDocument?.styleSheets ?? [],
- htmlString,
- nativeWidth,
- nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
- NumCast(this.layoutDoc._scrollTop)
- ).then
- ((dataUrl: any) => {
- VideoBox.convertDataUri(dataUrl, this.layoutDoc[Id] + "-thumb" + (new Date()).getTime(), true).then(
- returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500));
- })
- .catch(function (error: any) {
- console.error('oops, something went wrong!', error);
- });
- }
+ } else if (
+ (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
+ !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
+ LightboxView.LightboxDoc !== this.rootDoc
+ ) {
+ // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
+ this.updateThumb();
}
- });
+ },
+ { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) }
+ );
- this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight,
+ this._disposers.autoHeight = reaction(
+ () => this.layoutDoc._autoHeight,
autoHeight => {
if (autoHeight) {
- this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
- this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+ this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']);
+ this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
}
- });
+ }
+ );
- if (this.webField?.href.indexOf("youtube") !== -1) {
+ if (this.webField?.href.indexOf('youtube') !== -1) {
const youtubeaspect = 400 / 315;
const nativeWidth = Doc.NativeWidth(this.layoutDoc);
const nativeHeight = Doc.NativeHeight(this.layoutDoc);
@@ -181,42 +228,38 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
- var quickScroll = true;
- this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
- (scrollTop) => {
- if (quickScroll) this._initialScroll = scrollTop;
- else {
- const viewTrans = StrCast(this.Document._viewTransition);
- const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
- const durationSecStr = viewTrans.match(/([0-9.]*)s/);
- const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
- this.goTo(scrollTop, duration);
- }
+ this._disposers.scrollReaction = reaction(
+ () => NumCast(this.layoutDoc._scrollTop),
+ scrollTop => {
+ const viewTrans = StrCast(this.Document._viewTransition);
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ this.goTo(scrollTop, duration);
},
{ fireImmediately: true }
);
- quickScroll = false;
}
- componentWillUnmount() {
+ @action componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
- this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
+ // this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
+ // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
}
@action
- createTextAnnotation = (sel: Selection, selRange: Range) => {
- if (this._mainCont.current) {
+ createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
+ if (this._mainCont.current && selRange) {
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
if (rect && rect.width !== this._mainCont.current.clientWidth) {
- const annoBox = document.createElement("div");
- annoBox.className = "marqueeAnnotator-annotationBox";
+ const annoBox = document.createElement('div');
+ annoBox.className = 'marqueeAnnotator-annotationBox';
// transforms the positions from screen onto the pdf div
annoBox.style.top = (rect.top + this._mainCont.current.scrollTop).toString();
- annoBox.style.left = (rect.left).toString();
- annoBox.style.width = (rect.width).toString();
- annoBox.style.height = (rect.height).toString();
+ annoBox.style.left = rect.left.toString();
+ annoBox.style.width = rect.width.toString();
+ annoBox.style.height = rect.height.toString();
this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1);
}
}
@@ -224,69 +267,76 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
//this._selectionText = selRange.cloneContents().textContent || "";
// clear selection
- if (sel.empty) sel.empty();// Chrome
- else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
- }
+ if (sel.empty) sel.empty(); // Chrome
+ else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
+ return this._savedAnnotations;
+ };
menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
scrollFocus = (doc: Doc, smooth: boolean) => {
if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth);
- if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
+ if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
this.toggleSidebar(!smooth);
}
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
if (doc !== this.rootDoc && this._outerRef.current) {
- const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1);
- if (scrollTo !== undefined) {
+ const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight()));
+ if (scrollTo !== undefined && this._initialScroll === undefined) {
const focusSpeed = smooth ? 500 : 0;
- this._initialScroll !== undefined && (this._initialScroll = scrollTo);
this.goTo(scrollTo, focusSpeed);
return focusSpeed;
+ } else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) {
+ this._initialScroll = scrollTo;
}
}
- this._initialScroll = NumCast(this.layoutDoc._scrollTop);
- return 0;
- }
+ return undefined;
+ };
getAnchor = () => {
const anchor =
- AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
+ this._getAnchor(this._savedAnnotations) ??
Docs.Create.WebanchorDocument(this._url, {
- title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
+ title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._scrollTop),
y: NumCast(this.layoutDoc._scrollTop),
- unrendered: true
+ unrendered: true,
});
this.addDocumentWrapper(anchor);
return anchor;
- }
+ };
+
+ _textAnnotationCreator: (() => ObservableMap<number, HTMLDivElement[]>) | undefined;
+ savedAnnotationsCreator: () => ObservableMap<number, HTMLDivElement[]> = () => this._textAnnotationCreator?.() || this._savedAnnotations;
@action
iframeUp = (e: PointerEvent) => {
+ this._textAnnotationCreator = undefined;
this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
- const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
+ const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
const sel = this._iframe.contentWindow.getSelection();
if (sel) {
- this.createTextAnnotation(sel, sel.getRangeAt(0));
- AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX,
- e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale);
+ this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
+ AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale);
}
}
- }
+ };
@action
iframeDown = (e: PointerEvent) => {
+ const sel = this._iframe?.contentWindow?.getSelection?.();
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
- const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
+ const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
this._setPreviewCursor?.(e.clientX, e.clientY, false, true);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeing = [e.clientX * scale + mainContBounds.translateX,
- e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
- if (word || (e.target as any || "").className.includes("rangeslider") || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
- setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+ this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
+ if (word || ((e.target as any) || '').className.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
+ setTimeout(
+ action(() => (this._marqueeing = undefined)),
+ 100
+ ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
} else {
this._iframeClick = this._iframe ?? undefined;
this._isAnnotating = true;
@@ -302,20 +352,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
ContextMenu.Instance.closeMenu();
ContextMenu.Instance.setIgnoreEvents(true);
}
- }
+ };
getScrollHeight = () => this._scrollHeight;
isFirefox = () => {
- return "InstallTrigger" in window; // navigator.userAgent.indexOf("Chrome") !== -1;
- }
+ return 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;
+ };
iframeClick = () => this._iframeClick;
iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale;
@action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
- let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString());
+ if (this._initialScroll !== undefined) {
+ this.setScrollPos(this._initialScroll);
+ }
+ let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
if (requrlraw !== this._url.toString()) {
if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
@@ -323,85 +376,104 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (matches) {
requrlraw = requrlraw.substring(0, requrlraw.indexOf(newsearch));
for (let i = 1; i < Array.from(matches)?.length; i++) {
- requrlraw = requrlraw.replace(matches[i], "");
+ requrlraw = requrlraw.replace(matches[i], '');
}
}
- requrlraw = requrlraw.replace(/q=[^&]*/, newsearch.substring(1)).replace("search&", "search?").replace("?gbv=1", "");
+ requrlraw = requrlraw
+ .replace(/q=[^&]*/, newsearch.substring(1))
+ .replace('search&', 'search?')
+ .replace('?gbv=1', '');
}
this.submitURL(requrlraw, undefined, true);
}
if (iframe?.contentDocument) {
- iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
- iframe.contentDocument.addEventListener("pointerdown", this.iframeDown);
- this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
- setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
- iframe.setAttribute("enable-annotation", "true");
- iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => {
- let href = "";
- for (let ele = e.target as any; ele; ele = ele.parentElement) {
- href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
- }
- const origin = this.webField?.origin;
- if (href && origin) {
- e.stopPropagation();
- setTimeout(() => this.submitURL(href.replace(Utils.prepend(""), origin)));
- if (this._outerRef.current) {
- this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop);
- this._outerRef.current.scrollLeft = 0;
- }
- }
- })));
+ iframe.contentDocument.addEventListener('pointerup', this.iframeUp);
+ iframe.contentDocument.addEventListener('pointerdown', this.iframeDown);
+ this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument.body.scrollHeight);
+ setTimeout(
+ action(() => (this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0))),
+ 5000
+ );
+ iframe.setAttribute('enable-annotation', 'true');
+ iframe.contentDocument.addEventListener(
+ 'click',
+ undoBatch(
+ action((e: MouseEvent) => {
+ let href = '';
+ for (let ele = e.target as any; ele; ele = ele.parentElement) {
+ href = (typeof ele.href === 'string' ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
+ }
+ const origin = this.webField?.origin;
+ if (href && origin) {
+ e.stopPropagation();
+ setTimeout(() => this.submitURL(href.replace(Utils.prepend(''), origin)));
+ if (this._outerRef.current) {
+ this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop);
+ this._outerRef.current.scrollLeft = 0;
+ }
+ }
+ })
+ )
+ );
iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false);
//iframe.contentDocument.addEventListener('scroll', () => !this.active() && this._iframe && (this._iframe.scrollTop = NumCast(this.layoutDoc._scrollTop), false));
}
- }
+ };
@action
iframeWheel = (e: any) => {
if (!this._scrollTimer) {
- this._scrollTimer = setTimeout(action(() => this._scrollTimer = undefined), 250); // this turns events off on the iframe which allows scrolling to change direction smoothly
+ this._scrollTimer = setTimeout(
+ action(() => (this._scrollTimer = undefined)),
+ 250
+ ); // this turns events off on the iframe which allows scrolling to change direction smoothly
}
- }
+ };
@action
setDashScrollTop = (scrollTop: number, timeout: number = 250) => {
- const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
- timeout = scrollTop > iframeHeight ? 0 : timeout;
+ const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
this._scrollTimer && clearTimeout(this._scrollTimer);
- this._scrollTimer = setTimeout(action(() => {
- this._scrollTimer = undefined;
- if (!LinkDocPreview.LinkInfo && this._outerRef.current &&
- (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
- this.layoutDoc._scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
- }
- }), timeout);
- }
+ this._scrollTimer = setTimeout(
+ action(() => {
+ this._scrollTimer = undefined;
+ const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
+ if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
+ this.layoutDoc.thumb = undefined;
+ this.layoutDoc.thumbScrollTop = undefined;
+ this.layoutDoc.thumbNativeWidth = undefined;
+ this.layoutDoc.thumbNativeHeight = undefined;
+ this.layoutDoc.scrollTop = this._outerRef.current.scrollTop = newScrollTop;
+ } else if (this._outerRef.current) this._outerRef.current.scrollTop = newScrollTop;
+ }),
+ timeout
+ );
+ };
goTo = (scrollTop: number, duration: number) => {
if (this._outerRef.current) {
- const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
- scrollTop = scrollTop > iframeHeight + 50 ? iframeHeight : scrollTop;
+ const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
if (duration) {
smoothScroll(duration, [this._outerRef.current], scrollTop);
this.setDashScrollTop(scrollTop, duration);
} else {
this.setDashScrollTop(scrollTop);
}
- }
- }
+ } else this._initialScroll = scrollTop;
+ };
forward = (checkAvailable?: boolean) => {
- const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []);
- const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
+ const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string'), []);
+ const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []);
if (checkAvailable) return future.length;
runInAction(() => {
if (future.length) {
const curUrl = this._url;
- this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]);
+ this.dataDoc[this.fieldKey + '-history'] = new List<string>([...history, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!));
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(action(() => this._webUrl = this._url));
+ setTimeout(action(() => (this._webUrl = this._url)));
} else {
this._webUrl = this._url;
}
@@ -409,21 +481,21 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
});
return false;
- }
+ };
back = (checkAvailable?: boolean) => {
- const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"));
- const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
+ const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string'));
+ const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []);
if (checkAvailable) return history.length;
runInAction(() => {
if (history.length) {
const curUrl = this._url;
- if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
- else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]);
+ if (future === undefined) this.dataDoc[this.fieldKey + '-future'] = new List<string>([this._url]);
+ else this.dataDoc[this.fieldKey + '-future'] = new List<string>([...future, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(action(() => this._webUrl = this._url));
+ setTimeout(action(() => (this._webUrl = this._url)));
} else {
this._webUrl = this._url;
}
@@ -431,22 +503,33 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
});
return false;
- }
+ };
static urlHash = (s: string) => {
- return Math.abs(s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0));
- }
+ return Math.abs(
+ s.split('').reduce((a: any, b: any) => {
+ a = (a << 5) - a + b.charCodeAt(0);
+ return a & a;
+ }, 0)
+ );
+ };
@action
submitURL = (newUrl?: string, preview?: boolean, dontUpdateIframe?: boolean) => {
if (!newUrl) return;
- if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl;
+ if (!newUrl.startsWith('http')) newUrl = 'http://' + newUrl;
try {
- const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"));
- const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"));
+ const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string'));
+ const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'));
const url = this.webField?.toString();
if (url && !preview) {
- this.dataDoc[this.fieldKey + "-history"] = new List<string>([...(history || []), url]);
+ this.dataDoc[this.fieldKey + '-history'] = new List<string>([...(history || []), url]);
this.layoutDoc._scrollTop = 0;
+ if (this._webPageHasBeenRendered) {
+ this.layoutDoc.thumb = undefined;
+ this.layoutDoc.thumbScrollTop = undefined;
+ this.layoutDoc.thumbNativeWidth = undefined;
+ this.layoutDoc.thumbNativeHeight = undefined;
+ }
future && (future.length = 0);
}
if (!preview) {
@@ -454,47 +537,54 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
!dontUpdateIframe && (this._webUrl = this._url);
}
} catch (e) {
- console.log("WebBox URL error:" + this._url);
+ console.log('WebBox URL error:' + this._url);
}
return true;
- }
+ };
onWebUrlDrop = (e: React.DragEvent) => {
const { dataTransfer } = e;
- const html = dataTransfer.getData("text/html");
- const uri = dataTransfer.getData("text/uri-list");
- const url = uri || html || this._url || "";
- const newurl = url.startsWith(window.location.origin) ?
- url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
+ const html = dataTransfer.getData('text/html');
+ const uri = dataTransfer.getData('text/uri-list');
+ const url = uri || html || this._url || '';
+ const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || '') : url;
this.submitURL(newurl);
e.stopPropagation();
- }
+ };
onWebUrlValueKeyDown = (e: React.KeyboardEvent) => {
- e.key === "Enter" && this.submitURL(this._keyInput.current!.value);
+ e.key === 'Enter' && this.submitURL(this._keyInput.current!.value);
e.stopPropagation();
- }
+ };
@computed get urlEditor() {
return (
- <div className="collectionMenu-webUrlButtons" onDrop={this.onWebUrlDrop} onDragOver={e => e.preventDefault()} >
- <input className="collectionMenu-urlInput" key={this._url}
+ <div className="collectionMenu-webUrlButtons" onDrop={this.onWebUrlDrop} onDragOver={e => e.preventDefault()}>
+ <input
+ className="collectionMenu-urlInput"
+ key={this._url}
placeholder="ENTER URL"
defaultValue={this._url}
onDrop={this.onWebUrlDrop}
onDragOver={e => e.preventDefault()}
onKeyDown={this.onWebUrlValueKeyDown}
- onClick={(e) => {
+ onClick={e => {
this._keyInput.current!.select();
e.stopPropagation();
}}
ref={this._keyInput}
/>
- <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", maxWidth: "250px", }}>
+ <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', maxWidth: '250px' }}>
<button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}>
GO
</button>
- <button className="submitUrl" onClick={() => this.back}> <FontAwesomeIcon icon="caret-left" size="lg" /> </button>
- <button className="submitUrl" onClick={() => this.forward}> <FontAwesomeIcon icon="caret-right" size="lg" /> </button>
+ <button className="submitUrl" onClick={() => this.back}>
+ {' '}
+ <FontAwesomeIcon icon="caret-left" size="lg" />{' '}
+ </button>
+ <button className="submitUrl" onClick={() => this.forward}>
+ {' '}
+ <FontAwesomeIcon icon="caret-right" size="lg" />{' '}
+ </button>
</div>
</div>
);
@@ -503,44 +593,59 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
specificContextMenu = (e: React.MouseEvent | PointerEvent): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
- if (!cm.findByDescription("Options...")) {
- !Doc.UserDoc().noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
+ if (!cm.findByDescription('Options...')) {
+ !Doc.noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc.useCors = !this.layoutDoc.useCors), icon: 'snowflake' });
funcs.push({
- description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => {
+ description: (this.layoutDoc.allowScripts ? 'Prevent' : 'Allow') + ' Scripts',
+ event: () => {
this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts;
if (this._iframe) {
- runInAction(() => this._hackHide = true);
- setTimeout(action(() => this._hackHide = false));
+ runInAction(() => (this._hackHide = true));
+ setTimeout(action(() => (this._hackHide = false)));
}
- }, icon: "snowflake"
+ },
+ icon: 'snowflake',
});
funcs.push({
- description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => {
- const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
+ description: (!this.layoutDoc.forceReflow ? 'Force' : 'Prevent') + ' Reflow',
+ event: () => {
+ const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1);
this.layoutDoc.forceReflow = !nw;
if (nw) {
- Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true);
+ Doc.SetInPlace(this.layoutDoc, this.fieldKey + '-nativeWidth', nw, true);
}
- }, icon: "snowflake"
+ },
+ icon: 'snowflake',
});
- cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+ cm.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
}
- }
+ };
@action
onMarqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
- setupMoveUpEvents(this, e, action(e => {
- MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeing = [e.clientX, e.clientY];
- return true;
- }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }),
+ returnFalse,
+ () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
+ false
+ );
}
- }
+ };
@action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeing = undefined;
this._isAnnotating = false;
this._iframeClick = undefined;
+ const sel = this._iframe?.contentDocument?.getSelection();
+ if (sel?.empty) sel.empty(); // Chrome
+ else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
if (x !== undefined && y !== undefined) {
this._setPreviewCursor?.(x, y, false, false);
ContextMenu.Instance.closeMenu();
@@ -550,141 +655,228 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
}
}
- }
+ };
@computed get urlContent() {
- if (this._hackHide || (this.webThumb && (!this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc))) return (null);
+ if (this._hackHide || (this.webThumb && !this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc)) return null;
+ this.props.thumbShown?.();
const field = this.dataDoc[this.props.fieldKey];
let view;
if (field instanceof HtmlField) {
- view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
+ view = <span className="webBox-htmlSpan" contentEditable onPointerDown={e => e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl;
- view = <iframe className="webBox-iframe" enable-annotation={"true"}
- style={{ pointerEvents: this._scrollTimer ? "none" : undefined }}
- ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
- // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
- // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
- sandbox={`${this.layoutDoc.allowScripts ? "allow-scripts" : ""} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`} />;
+ view = (
+ <iframe
+ className="webBox-iframe"
+ enable-annotation={'true'}
+ style={{ pointerEvents: this._scrollTimer ? 'none' : undefined }}
+ ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
+ src={url}
+ onLoad={this.iframeLoaded}
+ // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
+ // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
+ sandbox={`${this.layoutDoc.allowScripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`}
+ />
+ );
} else {
- view = <iframe className="webBox-iframe" enable-annotation={"true"}
- style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
- ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />;
+ view = (
+ <iframe
+ className="webBox-iframe"
+ enable-annotation={'true'}
+ style={{ pointerEvents: this._scrollTimer ? 'none' : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
+ ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
+ src={'https://crossorigin.me/https://cs.brown.edu'}
+ />
+ );
}
- setTimeout(action(() => this._webPageHasBeenRendered = true));
+ setTimeout(
+ action(() => {
+ this._scrollHeight = Math.max(this._scrollHeight, this._iframe && this._iframe.contentDocument && this._iframe.contentDocument.body ? this._iframe.contentDocument.body.scrollHeight : 0);
+ if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
+ this.setScrollPos(NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop)));
+ }
+ this._webPageHasBeenRendered = true;
+ })
+ );
return view;
}
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
- (doc instanceof Doc ? [doc] : doc).forEach(doc => doc.webUrl = this._url);
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.webUrl = this._url));
return this.addDocument(doc, annotationKey);
- }
+ };
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
return this.addDocumentWrapper(doc, sidebarKey);
- }
+ };
@observable _draggingSidebar = false;
- sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
- setupMoveUpEvents(this, e, action((e, down, delta) => {
- this._draggingSidebar = true;
- const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]);
- const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
- const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
- const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth;
- if (ratio >= 1) {
- this.layoutDoc.nativeWidth = nativeWidth * ratio;
- onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => {
+ const batch = UndoManager.StartBatch('sidebar');
+ // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e, down, delta) => {
+ this._draggingSidebar = true;
+ const localDelta = this.props
+ .ScreenToLocalTransform()
+ .scale(this.props.NativeDimScaling?.() || 1)
+ .transformDirection(delta[0], delta[1]);
+ const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
+ const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + '-nativeHeight']);
+ const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
+ const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
+ if (ratio >= 1) {
+ this.layoutDoc.nativeWidth = nativeWidth * ratio;
+ this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
+ onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]);
+ this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ }
+ return false;
+ }),
+ action((e, movement, isClick) => {
+ this._draggingSidebar = false;
+ !isClick && batch.end();
+ }),
+ () => {
+ this.toggleSidebar();
+ batch.end();
}
- return false;
- }), action(() => this._draggingSidebar = false), () => this.toggleSidebar());
+ );
+ };
+ @computed get sidebarHandle() {
+ return (
+ <div
+ className="webBox-overlayButton-sidebar"
+ key="sidebar"
+ title="Toggle Sidebar"
+ style={{
+ display: !this.props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+ }}
+ onPointerDown={e => this.sidebarBtnDown(e, true)}>
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ </div>
+ );
}
@observable _previewNativeWidth: Opt<number> = undefined;
@observable _previewWidth: Opt<number> = undefined;
toggleSidebar = action((preview: boolean = false) => {
- const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
+ var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
+ if (!nativeWidth) {
+ const defaultNativeWidth = this.dataDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[WidthSym]();
+ Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
+ Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[HeightSym]() / this.Document[WidthSym]()) * defaultNativeWidth);
+ nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
+ }
const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth;
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
if (preview) {
this._previewNativeWidth = nativeWidth * sideratio;
- this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * sideratio / curNativeWidth;
+ this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth;
this._showSidebar = true;
- }
- else {
- this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
- this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * pdfratio / curNativeWidth;
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ } else {
+ this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar;
+ this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth;
+ if (!this.layoutDoc._showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) {
+ this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '-nativeWidth'] = undefined;
+ } else {
+ this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
+ }
}
});
- sidebarWidth = () => !this.SidebarShown ? 0 :
- this._previewWidth ? WebBox.openSidebarWidth :
- (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() /
- NumCast(this.layoutDoc.nativeWidth)
+ sidebarWidth = () =>
+ !this.SidebarShown ? 0 : WebBox.sidebarResizerWidth + (this._previewWidth ? WebBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth));
@computed get content() {
- const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
- return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
- style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
- {this.urlContent}
- </div>;
+ const interactive =
+ !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+ return (
+ <div className={'webBox-cont' + (interactive ? '-interactive' : '')} onKeyDown={e => e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}>
+ {this.urlContent}
+ </div>
+ );
}
@computed get annotationLayer() {
TraceMobx();
- const pe = this.pointerEvents();
- return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
- {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
- </div>;
-
+ return (
+ <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
+ {this.inlineTextAnnotations
+ .sort((a, b) => NumCast(a.y) - NumCast(b.y))
+ .map(anno => (
+ <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+ ))}
+ </div>
+ );
+ }
+ @computed get SidebarShown() {
+ return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
}
- @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
@computed get searchUI() {
- return <div className="webBox-ui"
- onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
- <div className="webBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
- <button className="webBox-overlayButton" title={"search"} />
- <input className="webBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
- onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
- <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
- <FontAwesomeIcon icon="search" size="sm" />
+ return (
+ <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
+ <div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
+ <button className="webBox-overlayButton" title={'search'} />
+ <input
+ className="webBox-searchBar"
+ placeholder="Search"
+ ref={this._searchRef}
+ onChange={this.searchStringChanged}
+ onKeyDown={e => {
+ e.key === 'Enter' && this.search(this._searchString, e.shiftKey);
+ e.stopPropagation();
+ }}
+ />
+ <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+ <FontAwesomeIcon icon="search" size="sm" />
+ </button>
+ </div>
+ <button
+ className="webBox-overlayButton"
+ title={'search'}
+ onClick={action(() => {
+ this._searching = !this._searching;
+ this.search('', false, true);
+ })}>
+ <div className="webBox-overlayButton-arrow" onPointerDown={e => e.stopPropagation()} />
+ <div className="webBox-overlayButton-iconCont" onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon icon={this._searching ? 'times' : 'search'} size="lg" />
+ </div>
</button>
</div>
- <button className="webBox-overlayButton" title={"search"}
- onClick={action(() => { this._searching = !this._searching; this.search("", false, true); })} >
- <div className="webBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()} />
- <div className="webBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon icon={this._searching ? "times" : "search"} size="lg" />
- </div>
- </button>
- </div>;
+ );
}
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
- showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
- panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
- panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
+ showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
+ panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
- basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
+ basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')];
transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
- childStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
- if (doc.textInlineAnnotations) return "none";
+ if (doc.textInlineAnnotations) return 'none';
}
return this.props.styleProvider?.(doc, props, property);
- }
- pointerEvents = () => !this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+ };
+ pointerEvents = () => (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
+ annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() ? 'all' : 'none');
render() {
- const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined;
+ const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
- const scale = previewScale * (this.props.scaling?.() || 1);
- const renderAnnotations = (docFilters?: () => string[]) =>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+ const renderAnnotations = (docFilters?: () => string[]) => (
+ <CollectionFreeFormView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
renderDepth={this.props.renderDepth + 1}
isAnnotationOverlay={true}
fieldKey={this.annotationKey}
@@ -693,88 +885,105 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
ScreenToLocalTransform={this.scrollXf}
- scaling={returnOne}
- dropAction={"alias"}
+ NativeDimScaling={returnOne}
+ dropAction={'alias'}
docFilters={docFilters || this.basicFilter}
dontRenderDocuments={docFilters ? false : true}
select={emptyFunction}
- ContentScaling={returnOne}
bringToFront={emptyFunction}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
- addDocument={this.sidebarAddDocument}
+ addDocument={this.addDocument}
styleProvider={this.childStyleProvider}
- childPointerEvents={this.props.isContentActive() ? "all" : undefined}
- pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
+ childPointerEvents={this.props.isContentActive() ? 'all' : undefined}
+ pointerEvents={this.annotationPointerEvents}
+ />
+ );
return (
- <div className="webBox" ref={this._mainCont}
- style={{ pointerEvents: this.pointerEvents(), display: this.props.thumbShown?.() ? "none" : undefined }} >
- <div className="webBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
- <div className="webBox-container" style={{
- position: "absolute",
- width: `calc(${100 / scale}% - ${(this.sidebarWidth() + WebBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`,
- transform: `scale(${scale})`,
- pointerEvents
- }} onContextMenu={this.specificContextMenu}>
- <div className={"webBox-outerContent"} ref={this._outerRef}
+ <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.pointerEvents(), display: this.props.thumbShown?.() ? 'none' : undefined }}>
+ <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} />
+ <div
+ className="webBox-container"
+ style={{
+ width: `calc(${100 / scale}% - ${!this.SidebarShown ? 0 : ((this.sidebarWidth() - WebBox.sidebarResizerWidth) / scale) * (this._previewWidth ? scale : 1)}px)`,
+ transform: `scale(${scale})`,
+ pointerEvents,
+ }}
+ onContextMenu={this.specificContextMenu}>
+ <div
+ className={'webBox-outerContent'}
+ ref={this._outerRef}
style={{
height: `${100 / scale}%`,
- pointerEvents
+ pointerEvents,
}}
- onWheel={e => { e.stopPropagation(); e.preventDefault(); }} // block wheel events from propagating since they're handled by the iframe
+ onWheel={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ }} // block wheel events from propagating since they're handled by the iframe
onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
- onPointerDown={this.onMarqueeDown}
- >
- <div className={"webBox-innerContent"} style={{ height: NumCast(this.scrollHeight, 50), pointerEvents }}>
+ onPointerDown={this.onMarqueeDown}>
+ <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered ? NumCast(this.scrollHeight, 50) : '100%', pointerEvents }}>
{this.content}
- <div style={{ mixBlendMode: "multiply" }}>
- {renderAnnotations(this.transparentFilter)}
- </div>
+ <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
{renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
+ {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
{this.annotationLayer}
</div>
</div>
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc}
- iframe={this.isFirefox() ? this.iframeClick : undefined}
- iframeScaling={this.isFirefox() ? this.iframeScaling : undefined}
- anchorMenuClick={this.anchorMenuClick}
- scrollTop={0}
- down={this._marqueeing} scaling={returnOne}
- addDocument={this.addDocumentWrapper}
- docView={this.props.docViewPath().lastElement()}
- finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
- annotationLayer={this._annotationLayer.current}
- mainCont={this._mainCont.current} />}
- </div >
- <SidebarAnnos ref={this._sidebarRef}
- {...this.props}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- fieldKey={this.fieldKey + "-" + this._urlHash}
- rootDoc={this.rootDoc}
- layoutDoc={this.layoutDoc}
- dataDoc={this.dataDoc}
- setHeight={emptyFunction}
- nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
- showSidebar={this.SidebarShown}
- sidebarAddDocument={this.sidebarAddDocument}
- moveDocument={this.moveDocument}
- removeDocument={this.removeDocument}
- />
- <div className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar"
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ <div style={{ transformOrigin: 'top left', transform: `scale(${1 / scale})` }}>
+ <MarqueeAnnotator
+ rootDoc={this.rootDoc}
+ iframe={this.isFirefox() ? this.iframeClick : undefined}
+ iframeScaling={this.isFirefox() ? this.iframeScaling : undefined}
+ anchorMenuClick={this.anchorMenuClick}
+ scrollTop={0}
+ down={this._marqueeing}
+ scaling={returnOne}
+ addDocument={this.addDocumentWrapper}
+ docView={this.props.docViewPath().lastElement()}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotationsCreator}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ />{' '}
+ </div>
+ )}
+ </div>
+ <div
+ className="webBox-sideResizer"
style={{
- display: !this.props.isContentActive() ? "none" : undefined,
- top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5,
- backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK
+ display: this.SidebarShown ? undefined : 'none',
+ width: WebBox.sidebarResizerWidth,
+ left: `calc(100% - ${this.sidebarWidth() - WebBox.sidebarResizerWidth}px)`,
}}
- onPointerDown={e => this.sidebarBtnDown(e, true)} >
- <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
+ onPointerDown={e => this.sidebarBtnDown(e, false)}
+ />
+ <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.layoutDoc[WidthSym]()}%` }}>
+ <SidebarAnnos
+ ref={this._sidebarRef}
+ {...this.props}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ fieldKey={this.fieldKey + '-' + this._urlHash}
+ rootDoc={this.rootDoc}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ setHeight={emptyFunction}
+ nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.NativeDimScaling?.() || 1)}
+ showSidebar={this.SidebarShown}
+ sidebarAddDocument={this.sidebarAddDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.removeDocument}
+ />
</div>
- {!this.props.isContentActive() ? (null) : this.searchUI}
- </div>);
+ {this.sidebarHandle}
+ {!this.props.isContentActive() ? null : this.searchUI}
+ </div>
+ );
}
}
-ScriptingGlobals.add(function urlHash(url: string) { return WebBox.urlHash(url); }); \ No newline at end of file
+ScriptingGlobals.add(function urlHash(url: string) {
+ return WebBox.urlHash(url);
+});
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index 08a5746d1..f3f1bcf5c 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -176,7 +176,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @returns {Promise<String>}
*/
- const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll) {
+ const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) {
return new Promise(async function (resolve, reject) {
@@ -214,8 +214,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
.replace(/noscript/g, "div").replace(/<div class="mediaset"><\/div>/g, "") // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
.replace(/<link[^>]*>/g, "") // don't need to keep any linked style sheets because we've already processed all style sheets above
.replace(/srcset="([^ "]*)[^"]*"/g, "src=\"$1\""); // instead of converting each item in the srcset to a data url, just convert the first one and use that
- let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml);
- const fetchedResources = await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml);
+ let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith("data:"));
+ const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : [];
for (let i = 0; i < fetchedResources.length; i++) {
const r = fetchedResources[i];
if (r.resourceUrl) {
@@ -240,7 +240,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
// build SVG string
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}'>
- <foreignObject x='0' y='${-scroll}' width='${width}' height='${scroll + height}'>
+ <foreignObject x='${-xoff}' y='${-scroll}' width='${xoff + width}' height='${scroll + height}'>
${contentRootElemString}
</foreignObject>
</svg>`;
@@ -259,11 +259,11 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<Image>}
*/
- this.renderToImage = async function (webUrl, html, width, height, scroll) {
+ this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) {
return new Promise(async function (resolve, reject) {
const img = new Image();
console.log("BUILDING SVG for:" + webUrl);
- img.src = await buildSvgDataUri(webUrl, html, width, height, scroll);
+ img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff);
img.onload = function () {
console.log("IMAGE SVG created:" + webUrl);
@@ -279,16 +279,16 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<Image>}
*/
- this.renderToCanvas = async function (webUrl, html, width, height, scroll) {
+ this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) {
return new Promise(async function (resolve, reject) {
- const img = await self.renderToImage(webUrl, html, width, height, scroll);
+ const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff);
const canvas = document.createElement('canvas');
- canvas.width = img.width;
- canvas.height = img.height;
+ canvas.width = img.width * oversample;
+ canvas.height = img.height * oversample;
const canvasCtx = canvas.getContext('2d');
- canvasCtx.drawImage(img, 0, 0, img.width, img.height);
+ canvasCtx.drawImage(img, 0, 0, img.width * oversample, img.height * oversample);
resolve(canvas);
});
@@ -301,9 +301,9 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<String>}
*/
- this.renderToBase64Png = async function (webUrl, html, width, height, scroll) {
+ this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) {
return new Promise(async function (resolve, reject) {
- const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll);
+ const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample);
resolve(canvas.toDataURL('image/png'));
});
};
@@ -311,8 +311,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
};
-export function CreateImage(webUrl, styleSheets, html, width, height, scroll) {
- const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll);
+export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) {
+ const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll, xoff, oversample);
return val;
}
diff --git a/src/client/views/nodes/button/ButtonScripts.ts b/src/client/views/nodes/button/ButtonScripts.ts
index f3731b8f9..b4a382faf 100644
--- a/src/client/views/nodes/button/ButtonScripts.ts
+++ b/src/client/views/nodes/button/ButtonScripts.ts
@@ -1,5 +1,6 @@
import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
import { SelectionManager } from "../../../util/SelectionManager";
+import { Colors } from "../../global/globalEnums";
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function changeView(view: string) {
@@ -8,7 +9,8 @@ ScriptingGlobals.add(function changeView(view: string) {
});
// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function toggleOverlay() {
+ScriptingGlobals.add(function toggleOverlay(readOnly?: boolean) {
const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ if (readOnly) return selected?.Document.z ? Colors.MEDIUM_BLUE : "transparent";
selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("failed");
}); \ No newline at end of file
diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx
index cf86b5e07..3b5aac221 100644
--- a/src/client/views/nodes/button/FontIconBadge.tsx
+++ b/src/client/views/nodes/button/FontIconBadge.tsx
@@ -7,30 +7,30 @@ import { DragManager } from "../../../util/DragManager";
import "./FontIconBadge.scss";
interface FontIconBadgeProps {
- collection: Doc | undefined;
+ value: string | undefined;
}
@observer
export class FontIconBadge extends React.Component<FontIconBadgeProps> {
_notifsRef = React.createRef<HTMLDivElement>();
- onPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e,
- (e: PointerEvent) => {
- const dragData = new DragManager.DocumentDragData([this.props.collection!]);
- DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
- return true;
- },
- returnFalse, emptyFunction, false);
- }
+ // onPointerDown = (e: React.PointerEvent) => {
+ // setupMoveUpEvents(this, e,
+ // (e: PointerEvent) => {
+ // const dragData = new DragManager.DocumentDragData([this.props.collection!]);
+ // DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
+ // return true;
+ // },
+ // returnFalse, emptyFunction, false);
+ // }
render() {
- if (!(this.props.collection instanceof Doc)) return (null);
- const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read
+ if (this.props.value === undefined) return (null);
return <div className="fontIconBadge-container" ref={this._notifsRef}>
- <div className="fontIconBadge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}
- onPointerDown={this.onPointerDown} >
- {length}
+ <div className="fontIconBadge" style={{ "display": "initial" }}
+ // onPointerDown={this.onPointerDown}
+ >
+ {this.props.value}
</div>
</div>;
}
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
index 079c767b9..a1ca777b3 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables';
.menuButton {
height: 100%;
@@ -18,12 +18,12 @@
.fontIconBox-label {
color: $white;
- position: relative;
+ bottom: 0;
+ position: absolute;
text-align: center;
font-size: 7px;
letter-spacing: normal;
background-color: inherit;
- margin-top: 5px;
border-radius: 8px;
padding: 0;
width: 100%;
@@ -31,8 +31,6 @@
text-transform: uppercase;
font-weight: bold;
transition: 0.15s;
-
-
}
.fontIconBox-icon {
@@ -40,8 +38,10 @@
height: 80%;
}
- &.clickBtn {
+ &.clickBtn,
+ &.clickBtnLabel {
cursor: pointer;
+ flex-direction: column;
&:hover {
background-color: rgba(0, 0, 0, 0.3) !important;
@@ -53,6 +53,12 @@
}
}
+ &.clickBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
&.textBtn {
display: grid;
/* grid-row: auto; */
@@ -64,12 +70,14 @@
justify-items: center;
&:hover {
- filter:brightness(0.85) !important;
+ filter: brightness(0.85) !important;
}
}
- &.tglBtn {
+ &.tglBtn,
+ &.tglBtnLabel {
cursor: pointer;
+ flex-direction: column;
&.switch {
//TOGGLE
@@ -96,31 +104,31 @@
right: 0;
bottom: 0;
background-color: lightgrey;
- -webkit-transition: .4s;
- transition: .4s;
+ -webkit-transition: 0.4s;
+ transition: 0.4s;
}
.slider:before {
position: absolute;
- content: "";
+ content: '';
height: 21px;
width: 21px;
left: 2px;
bottom: 2px;
background-color: $white;
- -webkit-transition: .4s;
- transition: .4s;
+ -webkit-transition: 0.4s;
+ transition: 0.4s;
}
- input:checked+.slider {
+ input:checked + .slider {
background-color: $medium-blue;
}
- input:focus+.slider {
+ input:focus + .slider {
box-shadow: 0 0 1px $medium-blue;
}
- input:checked+.slider:before {
+ input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
@@ -146,10 +154,19 @@
}
}
- &.toolBtn {
+ &.tglBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
+ &.toolBtn,
+ &.toolBtnLabel {
cursor: pointer;
width: 100%;
border-radius: 100%;
+ flex-direction: column;
+ margin-top: -4px;
svg {
width: 60% !important;
@@ -157,6 +174,12 @@
}
}
+ &.toolBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
&.menuBtn {
cursor: pointer !important;
border-radius: 0px;
@@ -166,15 +189,14 @@
width: 45% !important;
height: 45%;
}
-
- &:hover{
+
+ &:hover {
filter: brightness(0.85);
}
}
-
-
- &.colorBtn {
+ &.colorBtn,
+ &.colorBtnLabel {
color: black;
cursor: pointer;
flex-direction: column;
@@ -204,6 +226,12 @@
}
}
+ &.colorBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
&.drpdownList {
width: 100%;
display: grid;
@@ -278,6 +306,26 @@
background-color: #e3e3e3;
box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
border-radius: $standard-border-radius;
+
+ .menu-slider {
+ height: 10px;
+ }
+ input[type='range']::-webkit-slider-runnable-track {
+ background: gray;
+ height: 3px;
+ }
+
+ input[type='range']::-webkit-slider-thumb {
+ box-shadow: 1px 1px 1px #000000;
+ border: 1px solid #000000;
+ height: 10px;
+ width: 10px;
+ border-radius: 5px;
+ background: #ffffff;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -4px;
+ }
}
}
@@ -407,5 +455,4 @@
background: transparent;
position: fixed;
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index ca13590de..78ef85ec2 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -5,52 +5,49 @@ import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
-import { Doc, StrListCast } from '../../../../fields/Doc';
+import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
-import { createSchema } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { WebField } from '../../../../fields/URLField';
-import { Utils } from '../../../../Utils';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
+import { aggregateBounds, Utils } from '../../../../Utils';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
-import { CollectionViewType } from '../../collections/CollectionView';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { ContextMenu } from '../../ContextMenu';
import { DocComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { GestureOverlay } from '../../GestureOverlay';
import { Colors } from '../../global/globalEnums';
import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { InkTranscription } from '../../InkTranscription';
import { StyleProp } from '../../StyleProvider';
import { FieldView, FieldViewProps } from '.././FieldView';
import { RichTextMenu } from '../formattedText/RichTextMenu';
import { WebBox } from '../WebBox';
import { FontIconBadge } from './FontIconBadge';
import './FontIconBox.scss';
-const FontIconSchema = createSchema({
- icon: "string",
-});
export enum ButtonType {
- TextButton = "textBtn",
- MenuButton = "menuBtn",
- DropdownList = "drpdownList",
- DropdownButton = "drpdownBtn",
- ClickButton = "clickBtn",
- DoubleButton = "dblBtn",
- ToggleButton = "tglBtn",
- ColorButton = "colorBtn",
- ToolButton = "toolBtn",
- NumberButton = "numBtn",
- EditableText = "editableText"
+ TextButton = 'textBtn',
+ MenuButton = 'menuBtn',
+ DropdownList = 'drpdownList',
+ DropdownButton = 'drpdownBtn',
+ ClickButton = 'clickBtn',
+ DoubleButton = 'dblBtn',
+ ToggleButton = 'tglBtn',
+ ColorButton = 'colorBtn',
+ ToolButton = 'toolBtn',
+ NumberButton = 'numBtn',
+ EditableText = 'editableText',
}
export enum NumButtonType {
- Slider = "slider",
- DropdownOptions = "list",
- Inline = "inline"
+ Slider = 'slider',
+ DropdownOptions = 'list',
+ Inline = 'inline',
}
export interface ButtonProps extends FieldViewProps {
@@ -58,29 +55,52 @@ export interface ButtonProps extends FieldViewProps {
}
@observer
export class FontIconBox extends DocComponent<ButtonProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(FontIconBox, fieldKey);
+ }
showTemplate = (): void => {
const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this.props.addDocTab(dragFactory, "add:right");
- }
- dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); };
- useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); };
+ dragFactory && this.props.addDocTab(dragFactory, 'add:right');
+ };
+ dragAsTemplate = (): void => {
+ this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
+ };
+ useAsPrototype = (): void => {
+ this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)');
+ };
specificContextMenu = (): void => {
- if (!Doc.UserDoc().noviceMode) {
+ if (!Doc.noviceMode) {
const cm = ContextMenu.Instance;
- cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
- cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
- cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
+ cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' });
+ cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' });
+ cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' });
}
+ };
+
+ static GetShowLabels() {
+ return BoolCast(Doc.UserDoc()._showLabel);
+ }
+ static SetShowLabels(show: boolean) {
+ Doc.UserDoc()._showLabel = show;
}
// Determining UI Specs
- @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
- @observable private icon = StrCast(this.dataDoc.icon, "user") as any;
- @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen);
- @observable private buttonList: string[] = StrListCast(this.rootDoc.btnList);
- @observable private type = StrCast(this.rootDoc.btnType);
+ @computed get label() {
+ return StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
+ }
+ @computed get icon() {
+ return StrCast(this.dataDoc.icon, 'user') as any;
+ }
+ @computed get dropdown() {
+ return BoolCast(this.rootDoc.dropDownOpen);
+ }
+ @computed get buttonList() {
+ return StrListCast(this.rootDoc.btnList);
+ }
+ @computed get type() {
+ return StrCast(this.rootDoc.btnType);
+ }
/**
* Types of buttons in dash:
@@ -89,11 +109,11 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
* - Expandable button (CollectionLinearView)
* - Button inside of CollectionLinearView vs. outside of CollectionLinearView
* - Action button
- * - Dropdown button
+ * - Dropdown button
* - Color button
* - Dropdown list
* - Number button
- **/
+ **/
_batch: UndoManager.Batch | undefined = undefined;
/**
@@ -107,25 +127,32 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
// Script for checking the outcome of the toggle
const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0;
+ const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
+
if (numBtnType === NumButtonType.Slider) {
- const dropdown =
- <div
- className="menuButton-dropdownBox"
- onPointerDown={e => e.stopPropagation()}
- >
- <input type="range" step="1" min={NumCast(this.rootDoc.numBtnMin, 0)} max={NumCast(this.rootDoc.numBtnMax, 100)} value={checkResult}
- className={"menu-slider"} id="slider"
- onPointerDown={() => this._batch = UndoManager.StartBatch("presDuration")}
+ const dropdown = (
+ <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()}>
+ <input
+ type="range"
+ step="1"
+ min={NumCast(this.rootDoc.numBtnMin, 0)}
+ max={NumCast(this.rootDoc.numBtnMax, 100)}
+ value={checkResult}
+ className={'menu-slider'}
+ id="slider"
+ onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))}
onPointerUp={() => this._batch?.end()}
- onChange={e => { e.stopPropagation(); setValue(Number(e.target.value)); }}
+ onChange={e => {
+ e.stopPropagation();
+ setValue(Number(e.target.value));
+ }}
/>
- </div>;
+ </div>
+ );
return (
- <div
- className={`menuButton ${this.type} ${numBtnType}`}
- onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
- >
+ <div className={`menuButton ${this.type} ${numBtnType}`} onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
{checkResult}
+ {label}
{this.rootDoc.dropDownOpen ? dropdown : null}
</div>
);
@@ -136,62 +163,56 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
items.push(i);
}
}
- const list = items.map((value) => {
- return <div className="list-item" key={`${value}`}
- style={{
- backgroundColor: value === checkResult ? Colors.LIGHT_BLUE : undefined
- }}
- onClick={() => setValue(value)}>
- {value}
- </div>;
+ const list = items.map(value => {
+ return (
+ <div
+ className="list-item"
+ key={`${value}`}
+ style={{
+ backgroundColor: value === checkResult ? Colors.LIGHT_BLUE : undefined,
+ }}
+ onClick={() => setValue(value)}>
+ {value}
+ </div>
+ );
});
return (
- <div
- className={`menuButton ${this.type} ${numBtnType}`}
- >
- <div className={`button`} onClick={action((e) => setValue(Number(checkResult) - 1))}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"minus"} />
+ <div className={`menuButton ${this.type} ${numBtnType}`}>
+ <div className={`button`} onClick={action(e => setValue(Number(checkResult) - 1))}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'minus'} />
</div>
<div
className={`button ${'number'}`}
- onPointerDown={(e) => {
+ onPointerDown={e => {
e.stopPropagation();
e.preventDefault();
}}
- onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
- >
- <input
- style={{ width: 30 }}
- className="button-input"
- type="number"
- value={checkResult}
- onChange={action((e) => setValue(Number(e.target.value)))}
- />
+ onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
+ <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={action(e => setValue(Number(e.target.value)))} />
</div>
- <div className={`button`} onClick={action((e) => setValue(Number(checkResult) + 1))}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"plus"} />
+ <div className={`button`} onClick={action(e => setValue(Number(checkResult) + 1))}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} />
</div>
- {this.rootDoc.dropDownOpen ?
+ {this.rootDoc.dropDownOpen ? (
<div>
- <div className="menuButton-dropdownList"
- style={{ left: "25%" }}>
+ <div className="menuButton-dropdownList" style={{ left: '25%' }}>
{list}
</div>
- <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
- </div> : null}
-
+ <div
+ className="dropbox-background"
+ onClick={e => {
+ e.stopPropagation();
+ this.rootDoc.dropDownOpen = false;
+ }}
+ />
+ </div>
+ ) : null}
</div>
);
} else {
- return (
- <div>
-
- </div>
- );
+ return <div></div>;
}
-
-
}
/**
@@ -202,20 +223,21 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
return (
- <div className={`menuButton ${this.type} ${active}`}
+ <div
+ className={`menuButton ${this.type} ${active}`}
style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
- onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}>
+ onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {!this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
- <div
- className="menuButton-dropdown"
- style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
+ {!this.label || !FontIconBox.GetShowLabels() ? null : (
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
+ {' '}
+ {this.label}{' '}
+ </div>
+ )}
+ <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
<FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
</div>
- {this.rootDoc.dropDownOpen ?
- <div className="menuButton-dropdownBox">
- {/* DROPDOWN BOX CONTENTS */}
- </div> : null}
+ {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null}
</div>
);
}
@@ -229,11 +251,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const script = ScriptCast(this.rootDoc.script);
+ if (!script) {
+ return null;
+ }
let noviceList: string[] = [];
let text: string | undefined;
let dropdown = true;
- let icon: IconProp = "caret-down";
+ let icon: IconProp = 'caret-down';
try {
if (script.script.originalScript.startsWith('setView')) {
const selected = SelectionManager.Docs().lastElement();
@@ -242,125 +267,141 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
text = StrCast(selected._viewType);
} else {
dropdown = false;
- text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type);
+ text = selected.type === DocumentType.RTF ? 'Text' : StrCast(selected.type);
icon = Doc.toIcon(selected);
}
} else {
dropdown = false;
- icon = "globe-asia";
- text = "User Default";
+ icon = 'globe-asia';
+ text = 'User Default';
}
noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
} else if (script.script.originalScript.startsWith('setFont')) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
- noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia",
- "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
+ noviceList = ['Roboto', 'Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text'];
}
} catch (e) {
console.log(e);
}
// Get items to place into the list
- const list = this.buttonList.map((value) => {
- if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) {
+ const list = this.buttonList.map(value => {
+ if (Doc.noviceMode && !noviceList.includes(value)) {
return;
}
- return <div className="list-item" key={`${value}`}
- style={{
- fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined,
- backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined
- }}
- onClick={() => script.script.run({ value }).result}>
- {value[0].toUpperCase() + value.slice(1)}
- </div>;
+ return (
+ <div
+ className="list-item"
+ key={`${value}`}
+ style={{
+ fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined,
+ backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
+ }}
+ onClick={() => script.script.run({ value }).result}>
+ {value[0].toUpperCase() + value.slice(1)}
+ </div>
+ );
});
- const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
- {this.label}
- </div>;
+ const label =
+ !this.label || !FontIconBox.GetShowLabels() ? null : (
+ <div className="fontIconBox-label" style={{ bottom: 0, position: 'absolute', color: color, backgroundColor: backgroundColor }}>
+ {this.label}
+ </div>
+ );
return (
- <div className={`menuButton ${this.type} ${active}`}
- style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : "flex" }}
- onClick={dropdown ? () => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
- {dropdown ? (null) : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
- <div className="menuButton-dropdown-header">
- {text && text[0].toUpperCase() + text.slice(1)}
- </div>
+ <div
+ className={`menuButton ${this.type} ${active}`}
+ style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : 'flex' }}
+ onClick={dropdown ? () => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen) : undefined}>
+ {dropdown ? null : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
+ <div className="menuButton-dropdown-header">{text && text[0].toUpperCase() + text.slice(1)}</div>
{label}
- {!dropdown ? (null) : <div className="menuButton-dropDown">
- <FontAwesomeIcon icon={icon} color={color} size="sm" />
- </div>}
- {this.rootDoc.dropDownOpen ?
+ {!dropdown ? null : (
+ <div className="menuButton-dropDown">
+ <FontAwesomeIcon icon={icon} color={color} size="sm" />
+ </div>
+ )}
+ {this.rootDoc.dropDownOpen ? (
<div>
- <div className="menuButton-dropdownList"
- style={{ left: 0 }}>
+ <div className="menuButton-dropdownList" style={{ left: 0 }}>
{list}
</div>
- <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
+ <div
+ className="dropbox-background"
+ onClick={e => {
+ e.stopPropagation();
+ this.rootDoc.dropDownOpen = false;
+ }}
+ />
</div>
- : null}
+ ) : null}
</div>
);
}
@observable colorPickerClosed: boolean = true;
- @computed get colorScript() { return ScriptCast(this.rootDoc.script); }
+ @computed get colorScript() {
+ return ScriptCast(this.rootDoc.script);
+ }
colorPicker = (curColor: string) => {
const change = (value: ColorState) => {
const s = this.colorScript;
s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)();
};
- const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
- '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
- '#FFFFFF', '#f1efeb', "transparent"];
- return <SketchPicker
- onChange={change}
- color={curColor}
- presetColors={presets} />;
- }
+ const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent'];
+ return <SketchPicker onChange={change} color={curColor} presetColors={presets} />;
+ };
/**
* Color button
*/
@computed get colorButton() {
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent";
-
- const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
- {this.label}
- </div>;
-
- const dropdownCaret = <div
- className="menuButton-dropDown"
- style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
- <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
- </div>;
+ const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? 'transparent';
+
+ const label =
+ !this.label || !FontIconBox.GetShowLabels() ? null : (
+ <div className="fontIconBox-label" style={{ color, backgroundColor }}>
+ {this.label}
+ </div>
+ );
+
+ // dropdown caret seems superfluous since clicking the color button does the same thing
+ // const dropdownCaret = <div
+ // className="menuButton-dropDown"
+ // style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
+ // <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
+ // </div>;
setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView
return (
- <div className={`menuButton ${this.type} ${this.colorPickerClosed}`}
+ <div
+ className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')} ${this.colorPickerClosed}`}
style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
- onClick={action(() => this.colorPickerClosed = !this.colorPickerClosed)}
+ onClick={action(() => (this.colorPickerClosed = !this.colorPickerClosed))}
onPointerDown={e => e.stopPropagation()}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
<div className="colorButton-color" style={{ backgroundColor: curColor }} />
{label}
{/* {dropdownCaret} */}
- {this.colorPickerClosed ? (null) :
+ {this.colorPickerClosed ? null : (
<div>
- <div className="menuButton-dropdownBox"
- onPointerDown={e => e.stopPropagation()}
- onClick={e => e.stopPropagation()}>
+ <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()}>
{this.colorPicker(curColor)}
</div>
- <div className="dropbox-background" onClick={action((e) => {
- e.stopPropagation(); this.colorPickerClosed = true;
- })} />
- </div>}
+ <div
+ className="dropbox-background"
+ onPointerDown={action(e => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.colorPickerClosed = true;
+ })}
+ />
+ </div>
+ )}
</div>
);
}
@@ -374,27 +415,26 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
// Button label
- const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
- {this.label}
- </div>;
+ const label =
+ !this.label || !FontIconBox.GetShowLabels() ? null : (
+ <div className="fontIconBox-label" style={{ color, backgroundColor }}>
+ {this.label}
+ </div>
+ );
if (switchToggle) {
return (
<div className={`menuButton ${this.type} ${'switch'}`}>
{buttonText ? buttonText : null}
<label className="switch">
- <input type="checkbox"
- checked={backgroundColor === Colors.MEDIUM_BLUE}
- />
- <span className="slider round"></span>
+ <input type="checkbox" checked={backgroundColor === Colors.MEDIUM_BLUE} />
+ <span className="slider round" />
</label>
</div>
);
} else {
return (
- <div className={`menuButton ${this.type}`}
- style={{ opacity: 1, backgroundColor, color }}>
+ <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
{label}
</div>
@@ -402,8 +442,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
}
-
-
/**
* Default
*/
@@ -412,11 +450,15 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const active: string = StrCast(this.rootDoc.dropDownOpen);
return (
- <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu}
- style={{ backgroundColor: "transparent", borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
+ <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
<div className="menuButton-wrap">
- <FontAwesomeIcon className={`menuButton-icon-${this.type}`} icon={this.icon} color={"black"} size={"sm"} />
- {!this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
+ <FontAwesomeIcon className={`menuButton-icon-${this.type}`} icon={this.icon} color={'black'} size={'sm'} />
+ {!this.label || !FontIconBox.GetShowLabels() ? null : (
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
+ {' '}
+ {this.label}{' '}
+ </div>
+ )}
</div>
</div>
);
@@ -426,14 +468,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
// Script for running the toggle
const script = ScriptCast(this.rootDoc.script);
// Function to run the script
- const checkResult = script?.script.run({ value: "", _readOnly_: true }).result;
+ const checkResult = script?.script.run({ value: '', _readOnly_: true }).result;
const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result;
return (
<div className="menuButton editableText">
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"lock"} />
- <div style={{ width: "calc(100% - .875em)", paddingLeft: "4px" }}>
- <EditableView GetValue={() => script?.script.run({ value: "", _readOnly_: true }).result} SetValue={setValue} contents={checkResult} />
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
+ <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
+ <EditableView GetValue={() => script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} contents={checkResult} />
</div>
</div>
);
@@ -442,31 +484,31 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
render() {
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
- {this.label}
- </div>;
+ const label =
+ !this.label || !FontIconBox.GetShowLabels() ? null : (
+ <div className="fontIconBox-label" style={{ color, backgroundColor }}>
+ {this.label}
+ </div>
+ );
- const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor: "transparent" }}>
- {this.label}
- </div>;
+ const menuLabel =
+ !this.label || !FontIconBox.GetShowLabels() ? null : (
+ <div className="fontIconBox-label" style={{ color, backgroundColor: 'transparent' }}>
+ {this.label}
+ </div>
+ );
const buttonText = StrCast(this.rootDoc.buttonText);
// TODO:glr Add label of button type
- let button = this.defaultButton;
+ let button: JSX.Element | null = this.defaultButton;
switch (this.type) {
case ButtonType.TextButton:
button = (
<div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {buttonText ?
- <div className="button-text">
- {buttonText}
- </div>
- : null}
+ {buttonText ? <div className="button-text">{buttonText}</div> : null}
{label}
</div>
);
@@ -489,7 +531,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
break;
case ButtonType.ToolButton:
button = (
- <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor, color }}>
+ <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
{label}
</div>
@@ -501,49 +543,47 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
break;
case ButtonType.ClickButton:
button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1 }}>
+ <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ color, backgroundColor, opacity: 1 }}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
{label}
</div>
);
break;
case ButtonType.MenuButton:
- const trailsIcon = <img src={`/assets/${"presTrails.png"}`}
- style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />;
+ const trailsIcon = <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />;
button = (
<div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
- {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
+ {this.icon === 'pres-trail' ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
{menuLabel}
- <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
- </div >
+ <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
+ </div>
);
break;
default:
break;
}
- return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button :
- <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
- {button}
- </Tooltip>;
+ return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
+ button
+ ) : button !== null ? (
+ <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
+ ) : null;
}
}
-
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setView(view: string) {
const selected = SelectionManager.Docs().lastElement();
- selected ? selected._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
+ selected ? (selected._viewType = view) : console.log('[FontIconBox.tsx] changeView failed');
});
-
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
+ const selected = SelectionManager.Views().lastElement();
if (checkResult) {
- return selected?._backgroundColor ?? "transparent";
+ return selected?.props.Document._backgroundColor ?? 'transparent';
}
- if (selected) selected._backgroundColor = color;
+ if (selected) selected.props.Document._backgroundColor = color;
});
// toggle: Set overlay status of selected document
@@ -553,17 +593,17 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
}
Doc.SharingDoc().userColor = undefined;
Doc.GetProto(Doc.SharingDoc()).userColor = color;
- Doc.UserDoc().showTitle = color === "transparent" ? undefined : StrCast(Doc.UserDoc().showTitle, "creationDate");
+ Doc.UserDoc().showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().showTitle, 'creationDate');
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
- if (checkResult && selected) {
- if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE;
- return "transparent";
+ if (checkResult) {
+ if (NumCast(selected?.Document.z) >= 1) return Colors.MEDIUM_BLUE;
+ return 'transparent';
}
- selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
+ selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
});
/** TEXT
@@ -580,7 +620,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) {
- SelectionManager.Docs().map(doc => doc._fontFamily = font);
+ SelectionManager.Docs().map(doc => (doc._fontFamily = font));
const editorView = RichTextMenu.Instance.TextView?.EditorView;
if (checkResult) {
return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
@@ -589,38 +629,38 @@ ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) {
else Doc.UserDoc().fontFamily = font;
});
-ScriptingGlobals.add(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") {
+ScriptingGlobals.add(function getActiveTextInfo(info: 'family' | 'size' | 'color' | 'highlight') {
const editorView = RichTextMenu.Instance.TextView?.EditorView;
const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
switch (info) {
- case "family": return style?.activeFamilies[0];
- case "size": return style?.activeSizes[0];
- case "color": return style?.activeColors[0];
- case "highlight": return style?.activeHighlights[0];
+ case 'family':
+ return style?.activeFamilies[0];
+ case 'size':
+ return style?.activeSizes[0];
+ case 'color':
+ return style?.activeColors[0];
+ case 'highlight':
+ return style?.activeHighlights[0];
}
});
-ScriptingGlobals.add(function setAlignment(align: "left" | "right" | "center", checkResult?: boolean) {
+ScriptingGlobals.add(function setAlignment(align: 'left' | 'right' | 'center', checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return (editorView ? RichTextMenu.Instance.textAlign : Doc.UserDoc().textAlign) === align ? Colors.MEDIUM_BLUE : "transparent";
+ return (editorView ? RichTextMenu.Instance.textAlign : Doc.UserDoc().textAlign) === align ? Colors.MEDIUM_BLUE : 'transparent';
}
if (editorView?.state) RichTextMenu.Instance.align(editorView, editorView.dispatch, align);
else Doc.UserDoc().textAlign = align;
});
-ScriptingGlobals.add(function setBulletList(mapStyle: "bullet" | "decimal", checkResult?: boolean) {
+ScriptingGlobals.add(function setBulletList(mapStyle: 'bullet' | 'decimal', checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
if (active === mapStyle) return Colors.MEDIUM_BLUE;
- return "transparent";
- }
- if (editorView) {
- const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
- editorView?.state && RichTextMenu.Instance.changeListType(
- editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }));
+ return 'transparent';
}
+ editorView?.state && RichTextMenu.Instance.changeListType(mapStyle);
});
// toggle: Set overlay status of selected document
@@ -656,72 +696,160 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).replace("px", "");
+ return RichTextMenu.Instance.fontSize.replace('px', '');
}
- if (typeof size === "number") size = size.toString();
- if (size && Number(size).toString() === size) size += "px";
+ if (typeof size === 'number') size = size.toString();
+ if (size && Number(size).toString() === size) size += 'px';
if (editorView) RichTextMenu.Instance.setFontSize(size);
else Doc.UserDoc()._fontSize = size;
});
+ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) {
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ return (editorView ? RichTextMenu.Instance.noAutoLink : false) ? Colors.MEDIUM_BLUE : 'transparent';
+ }
+ if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor();
+});
ScriptingGlobals.add(function toggleBold(checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === "bold") ? Colors.MEDIUM_BLUE : "transparent";
+ return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === 'bold') ? Colors.MEDIUM_BLUE : 'transparent';
}
if (editorView) RichTextMenu.Instance?.toggleBold();
- else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === "bold" ? undefined : "bold";
+ else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold';
});
ScriptingGlobals.add(function toggleUnderline(checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === "underline") ? Colors.MEDIUM_BLUE : "transparent";
+ return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === 'underline') ? Colors.MEDIUM_BLUE : 'transparent';
}
if (editorView) RichTextMenu.Instance?.toggleUnderline();
- else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === "underline" ? undefined : "underline";
+ else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline';
});
ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === "italics") ? Colors.MEDIUM_BLUE : "transparent";
+ return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === 'italics') ? Colors.MEDIUM_BLUE : 'transparent';
}
if (editorView) RichTextMenu.Instance?.toggleItalics();
- else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics";
+ else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics';
});
+export function checkInksToGroup() {
+ // console.log("getting here to inks group");
+ if (Doc.ActiveTool === InkTool.Write) {
+ CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+ // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+ // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
+ const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => {
+ // console.log(inkDoc.x, inkDoc.y);
+ });
+ });
+ }
+}
+
+export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
+ // TODO nda - if document being added to is a inkGrouping then we can just add to that group
+ if (Doc.ActiveTool === InkTool.Write) {
+ CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+ // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+ const selected = ffView.unprocessedDocs;
+ // loop through selected an get the bound
+ const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
+
+ selected.map(
+ action(d => {
+ const x = NumCast(d.x);
+ const y = NumCast(d.y);
+ const width = d[WidthSym]();
+ const height = d[HeightSym]();
+ bounds.push({ x, y, width, height });
+ })
+ );
+
+ const aggregBounds = aggregateBounds(bounds, 0, 0);
+ const marqViewRef = ffView._marqueeViewRef.current;
+
+ // set the vals for bounds in marqueeView
+ if (marqViewRef) {
+ marqViewRef._downX = aggregBounds.x;
+ marqViewRef._downY = aggregBounds.y;
+ marqViewRef._lastX = aggregBounds.r;
+ marqViewRef._lastY = aggregBounds.b;
+ }
+
+ selected.map(
+ action(d => {
+ const dx = NumCast(d.x);
+ const dy = NumCast(d.y);
+ delete d.x;
+ delete d.y;
+ delete d.activeFrame;
+ delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ // calculate pos based on bounds
+ if (marqViewRef?.Bounds) {
+ d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+ d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+ }
+ return d;
+ })
+ );
+ ffView.props.removeDocument?.(selected);
+ // TODO: nda - this is the code to actually get a new grouped collection
+ const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+ if (newCollection) {
+ newCollection.height = newCollection[HeightSym]();
+ newCollection.width = newCollection[WidthSym]();
+ }
+ // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+ newCollection && ffView.props.addDocument?.(newCollection);
+ // TODO: nda - will probably need to go through and only remove the unprocessed selected docs
+ ffView.unprocessedDocs = [];
+ InkTranscription.Instance.transcribeInk(newCollection, selected, false);
+ });
+ }
+ CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+}
/** INK
- * setActiveInkTool
+ * setActiveTool
* setStrokeWidth
* setStrokeColor
**/
-ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) {
+ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) {
+ InkTranscription.Instance?.createInkGroup();
if (checkResult) {
- return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ?
- Colors.MEDIUM_BLUE : "transparent";
+ return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent';
}
- if (["circle", "square", "line"].includes(tool)) {
+ if (['circle', 'square', 'line'].includes(tool)) {
if (GestureOverlay.Instance.InkShape === tool) {
- Doc.UserDoc().activeInkTool = InkTool.None;
+ Doc.ActiveTool = InkTool.None;
GestureOverlay.Instance.InkShape = InkTool.None;
} else {
- Doc.UserDoc().activeInkTool = InkTool.Pen;
+ Doc.ActiveTool = InkTool.Pen;
GestureOverlay.Instance.InkShape = tool;
}
- } else if (tool) { // pen or eraser
- if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) {
- Doc.UserDoc().activeInkTool = InkTool.None;
+ } else if (tool) {
+ // pen or eraser
+ if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) {
+ Doc.ActiveTool = InkTool.None;
+ } else if (tool == InkTool.Write) {
+ // console.log("write mode selected - create groupDoc here!", tool)
+ Doc.ActiveTool = tool;
+ GestureOverlay.Instance.InkShape = '';
} else {
- Doc.UserDoc().activeInkTool = tool;
- GestureOverlay.Instance.InkShape = "";
+ Doc.ActiveTool = tool as any;
+ GestureOverlay.Instance.InkShape = '';
}
} else {
- Doc.UserDoc().activeInkTool = InkTool.None;
+ Doc.ActiveTool = InkTool.None;
}
});
@@ -735,7 +863,9 @@ ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean
return ActiveFillColor();
}
SetActiveFillColor(StrCast(color));
- SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.fillColor = color);
+ SelectionManager.Docs()
+ .filter(doc => doc.type === DocumentType.INK)
+ .map(doc => (doc.fillColor = color));
});
ScriptingGlobals.add(function setStrokeWidth(width: number, checkResult?: boolean) {
@@ -747,7 +877,9 @@ ScriptingGlobals.add(function setStrokeWidth(width: number, checkResult?: boolea
return ActiveInkWidth();
}
SetActiveInkWidth(width.toString());
- SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.strokeWidth = Number(width));
+ SelectionManager.Docs()
+ .filter(doc => doc.type === DocumentType.INK)
+ .map(doc => (doc.strokeWidth = Number(width)));
});
// toggle: Set overlay status of selected document
@@ -760,10 +892,11 @@ ScriptingGlobals.add(function setStrokeColor(color?: string, checkResult?: boole
return ActiveInkColor();
}
SetActiveInkColor(StrCast(color));
- SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.color = String(color));
+ SelectionManager.Docs()
+ .filter(doc => doc.type === DocumentType.INK)
+ .map(doc => (doc.color = String(color)));
});
-
/** WEB
* webSetURL
**/
@@ -778,21 +911,20 @@ ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
}
});
ScriptingGlobals.add(function webForward(checkResult?: boolean) {
- const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox);
+ const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
if (checkResult) {
- return selected?.forward(checkResult) ? undefined : "lightGray";
+ return selected?.forward(checkResult) ? undefined : 'lightGray';
}
selected?.forward();
});
ScriptingGlobals.add(function webBack(checkResult?: boolean) {
- const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox);
+ const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
if (checkResult) {
- return selected?.back(checkResult) ? undefined : "lightGray";
+ return selected?.back(checkResult) ? undefined : 'lightGray';
}
selected?.back();
});
-
/** Schema
* toggleSchemaPreview
**/
@@ -801,13 +933,12 @@ ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
if (checkResult && selected) {
const result: boolean = NumCast(selected.schemaPreviewWidth) > 0;
if (result) return Colors.MEDIUM_BLUE;
- else return "transparent";
- }
- else if (selected) {
+ else return 'transparent';
+ } else if (selected) {
if (NumCast(selected.schemaPreviewWidth) > 0) {
- selected.schemaPreviewWidth = 200;
- } else {
selected.schemaPreviewWidth = 0;
+ } else {
+ selected.schemaPreviewWidth = 200;
}
}
});
@@ -816,11 +947,11 @@ ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
* groupBy
*/
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
- SelectionManager.Docs().map(doc => doc._fontFamily = key);
+ SelectionManager.Docs().map(doc => (doc._fontFamily = key));
const editorView = RichTextMenu.Instance.TextView?.EditorView;
if (checkResult) {
return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
}
if (editorView) RichTextMenu.Instance.setFontFamily(key);
else Doc.UserDoc().fontFamily = key;
-}); \ No newline at end of file
+});
diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
index 235495250..7f414ddbb 100644
--- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
+++ b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
@@ -5,6 +5,7 @@ import { IButtonProps } from '../ButtonInterface';
import { ColorState, SketchPicker } from 'react-color';
import { ScriptField } from '../../../../../fields/ScriptField';
import { Doc } from '../../../../../fields/Doc';
+import { FontIconBox } from '../FontIconBox';
export class ColorDropdown extends Component<IButtonProps> {
render() {
@@ -31,7 +32,7 @@ export class ColorDropdown extends Component<IButtonProps> {
disableAlpha={!stroke}
onChange={func} color={boolResult ? boolResult : "#FFFFFF"}
presetColors={colorOptions} />;
- const label = !this.props.label || !Doc.UserDoc()._showLabel ? (null) :
+ const label = !this.props.label || !FontIconBox.GetShowLabels() ? (null) :
<div className="fontIconBox-label" style={{ color: this.props.color, backgroundColor: this.props.backgroundColor, position: "absolute" }}>
{this.props.label}
</div>;
diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx
index e18590a95..5d7d55863 100644
--- a/src/client/views/nodes/button/textButton/TextButton.tsx
+++ b/src/client/views/nodes/button/textButton/TextButton.tsx
@@ -9,9 +9,22 @@ export class TextButton extends Component<IButtonProps> {
// Determine the type of toggle button
const buttonText: boolean = BoolCast(this.props.rootDoc.switchToggle);
- return (<div className={`menuButton ${this.props.type}`} style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
- {this.props.label}
- </div>);
+ return (
+ <div
+ className={`menuButton ${this.props.type}`}
+ style={{
+ opacity: 1,
+ backgroundColor: this.props.backgroundColor,
+ color: this.props.color,
+ }}
+ >
+ <FontAwesomeIcon
+ className={`fontIconBox-icon-${this.props.type}`}
+ icon={this.props.icon}
+ color={this.props.color}
+ />
+ {this.props.label}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index 5c75a589a..40dd6fbc7 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,37 +1,44 @@
-import { TextSelection } from "prosemirror-state";
+import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../../fields/Doc";
-import { DocServer } from "../../../DocServer";
-import React = require("react");
-
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
+import React = require('react');
// creates an inline comment in a note when '>>' is typed.
// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
// the comment can be toggled on/off with the '<-' text anchor.
export class DashDocCommentView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any) {
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.fontWeight = "bold";
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
- ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.fontWeight = 'bold';
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this.dom);
+ (this as any).dom = this.dom;
}
destroy() {
- ReactDOM.unmountComponentAtNode(this._fieldWrapper);
+ ReactDOM.unmountComponentAtNode(this.dom);
}
- selectNode() { }
+ selectNode() {}
}
interface IDashDocCommentViewInternal {
@@ -40,8 +47,7 @@ interface IDashDocCommentViewInternal {
getPos: any;
}
-export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal>{
-
+export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
constructor(props: IDashDocCommentViewInternal) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -71,7 +77,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
- try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
+ try {
+ this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
+ } catch (e) {}
}, 0);
}
e.stopPropagation();
@@ -81,32 +89,35 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
e.stopPropagation();
}
- targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+ targetNode = () => {
+ // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
const state = this.props.view.state;
for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
const m = state.doc.nodeAt(i);
if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docid === this.props.docid) {
- return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+ return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any; pos: number; hidden: boolean };
}
}
- const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.docid, float: "right" });
+ const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: this.props.docid, float: 'right' });
this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc));
- setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
+ setTimeout(() => {
+ try {
+ this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
+ } catch (e) {}
+ }, 0);
return undefined;
- }
+ };
render() {
return (
<span
className="formattedTextBox-inlineComment"
- id={"DashDocCommentView-" + this.props.docid}
+ id={'DashDocCommentView-' + this.props.docid}
onPointerLeave={this.onPointerLeaveCollapsed}
onPointerEnter={this.onPointerEnterCollapsed}
onPointerUp={this.onPointerUpCollapsed}
- onPointerDown={this.onPointerDownCollapsed}
- >
- </span>
+ onPointerDown={this.onPointerDownCollapsed}></span>
);
}
}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 364be461f..73a711b9d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,52 +1,53 @@
-import { IReactionDisposer, reaction, observable, action } from "mobx";
-import { NodeSelection } from "prosemirror-state";
-import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc";
-import { Cast, StrCast, NumCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { Transform } from "../../../util/Transform";
-import { DocumentView } from "../DocumentView";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { NodeSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom';
-import { observer } from "mobx-react";
-import { ColorScheme } from "../../../util/SettingsManager";
+import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
+import { DocServer } from '../../../DocServer';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { ColorScheme } from '../../../util/SettingsManager';
+import { Transform } from '../../../util/Transform';
+import { DocumentView } from '../DocumentView';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
export class DashDocView {
- _fieldWrapper: HTMLSpanElement; // container for label and value
+ dom: HTMLSpanElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this._fieldWrapper = document.createElement("span");
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.textIndent = "0";
- this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "lightGray"));
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block";
- (this._fieldWrapper.style as any).float = node.attrs.float;
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
- ReactDOM.render(<DashDocViewInternal
- docid={node.attrs.docid}
- alias={node.attrs.alias}
- width={node.attrs.width}
- height={node.attrs.height}
- hidden={node.attrs.hidden}
- fieldKey={node.attrs.fieldKey}
- tbox={tbox}
- view={view}
- node={node}
- getPos={getPos}
- />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ this.dom = document.createElement('span');
+ this.dom.style.position = 'relative';
+ this.dom.style.textIndent = '0';
+ this.dom.style.border = '1px solid ' + StrCast(tbox.layoutDoc.color, Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'dimgray' : 'lightGray');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
+ (this.dom.style as any).float = node.attrs.float;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ ReactDOM.render(
+ <DashDocViewInternal docid={node.attrs.docid} alias={node.attrs.alias} width={node.attrs.width} height={node.attrs.height} hidden={node.attrs.hidden} fieldKey={node.attrs.fieldKey} tbox={tbox} view={view} node={node} getPos={getPos} />,
+ this.dom
+ );
+ (this as any).dom = this.dom;
}
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
}
interface IDashDocViewInternal {
@@ -69,7 +70,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
@observable _dashDoc: Doc | undefined;
@observable _finalLayout: any;
@observable _resolvedDataDoc: any;
-
+ @observable _width: number = 0;
+ @observable _height: number = 0;
updateDoc = action((dashDoc: Doc) => {
this._dashDoc = dashDoc;
@@ -81,13 +83,20 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
}
this._resolvedDataDoc = Cast(this._finalLayout.resolvedDataDoc, Doc, null);
}
- if (this.props.width !== (this._dashDoc?._width ?? "") + "px" || this.props.height !== (this._dashDoc?._height ?? "") + "px") {
- try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
- ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px"
- }));
+ if (this.props.width !== (this._dashDoc?._width ?? '') + 'px' || this.props.height !== (this._dashDoc?._height ?? '') + 'px') {
+ try {
+ this._width = NumCast(this._dashDoc?._width);
+ this._height = NumCast(this._dashDoc?._height);
+ // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+ this.props.view.dispatch(
+ this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
+ ...this.props.node.attrs,
+ width: this._width + 'px',
+ height: this._height + 'px',
+ })
+ );
} catch (e) {
- console.log("DashDocView:" + e);
+ console.log('DashDocView:' + e);
}
}
});
@@ -98,14 +107,15 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
DocServer.GetRefField(this.props.docid + this.props.alias).then(async dashDoc => {
if (!(dashDoc instanceof Doc)) {
- this.props.alias && DocServer.GetRefField(this.props.docid).then(async dashDocBase => {
- if (dashDocBase instanceof Doc) {
- const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias);
- aliasedDoc.layoutKey = "layout";
- this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined);
- this.updateDoc(aliasedDoc);
- }
- });
+ this.props.alias &&
+ DocServer.GetRefField(this.props.docid).then(async dashDocBase => {
+ if (dashDocBase instanceof Doc) {
+ const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias);
+ aliasedDoc.layoutKey = 'layout';
+ this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined);
+ this.updateDoc(aliasedDoc);
+ }
+ });
} else {
this.updateDoc(dashDoc);
}
@@ -113,67 +123,70 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
}
componentDidMount() {
- this._disposers.upater = reaction(() => this._dashDoc && (NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width)),
- () => {
+ this._disposers.upater = reaction(
+ () => this._dashDoc && NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width),
+ action(() => {
if (this._dashDoc) {
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
- ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px"
- }));
+ this._width = NumCast(this._dashDoc._width);
+ this._height = NumCast(this._dashDoc._height);
+ const parent = this._spanRef.current?.parentNode as HTMLElement;
+ if (parent) {
+ parent.style.width = this._width + 'px';
+ parent.style.height = this._height + 'px';
+ }
}
- });
+ })
+ );
}
-
removeDoc = () => {
- this.props.view.dispatch(this.props.view.state.tr
- .setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.getPos())))
- .deleteSelection());
+ this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.getPos()))).deleteSelection());
return true;
- }
+ };
getDocTransform = () => {
if (!this._spanRef.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
return new Transform(-translateX, -translateY, 1).scale(1 / scale);
- }
- outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
+ };
+ outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
onKeyDown = (e: any) => {
e.stopPropagation();
- if (e.key === "Tab" || e.key === "Enter") {
+ if (e.key === 'Tab' || e.key === 'Enter') {
e.preventDefault();
}
- }
+ };
onPointerLeave = () => {
- const ele = document.getElementById("DashDocCommentView-" + this.props.docid) as HTMLDivElement;
- ele && (ele.style.backgroundColor = "");
- }
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ ele && (ele.style.backgroundColor = '');
+ };
onPointerEnter = () => {
- const ele = document.getElementById("DashDocCommentView-" + this.props.docid) as HTMLDivElement;
- ele && (ele.style.backgroundColor = "orange");
- }
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ ele && (ele.style.backgroundColor = 'orange');
+ };
componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
render() {
- return !this._dashDoc || !this._finalLayout || this.props.hidden ? null :
- <div ref={this._spanRef}
+ return !this._dashDoc || !this._finalLayout || this.props.hidden ? null : (
+ <div
+ ref={this._spanRef}
className="dash-span"
style={{
- width: this.props.width,
- height: this.props.height,
+ width: this._width,
+ height: this._height,
position: 'absolute',
- display: 'inline-block'
+ display: 'inline-block',
}}
onPointerLeave={this.onPointerLeave}
onPointerEnter={this.onPointerEnter}
onKeyDown={this.onKeyDown}
onKeyPress={e => e.stopPropagation()}
onKeyUp={e => e.stopPropagation()}
- onWheel={e => e.preventDefault()}
- >
+ onWheel={e => e.preventDefault()}>
<DocumentView
Document={this._finalLayout}
DataDoc={this._resolvedDataDoc}
@@ -182,7 +195,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
removeDocument={this.removeDoc}
isDocumentActive={returnFalse}
isContentActive={this._textBox.props.isContentActive}
- layerProvider={this._textBox.props.layerProvider}
styleProvider={this._textBox.props.styleProvider}
docViewPath={this._textBox.props.docViewPath}
ScreenToLocalTransform={this.getDocTransform}
@@ -201,6 +213,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
ContainingCollectionView={this._textBox.props.ContainingCollectionView}
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
/>
- </div>;
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 34908e54b..8c8b74560 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,46 +1,55 @@
-import { action, computed, IReactionDisposer, observable } from "mobx";
-import { observer } from "mobx-react";
+import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import { DataSym, Doc, DocListCast, Field } 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, StrCast } from "../../../../fields/Types";
-import { DocServer } from "../../../DocServer";
-import { DocUtils } from "../../../documents/Documents";
-import { CollectionViewType } from "../../collections/CollectionView";
-import "./DashFieldView.scss";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
+import { DataSym, Doc, DocListCast, Field } 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, StrCast } from '../../../../fields/Types';
+import { DocServer } from '../../../DocServer';
+import './DashFieldView.scss';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { Tooltip } from '@material-ui/core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
export class DashFieldView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.fontWeight = "bold";
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
- ReactDOM.render(<DashFieldViewInternal
- fieldKey={node.attrs.fieldKey}
- docid={node.attrs.docid}
- width={node.attrs.width}
- height={node.attrs.height}
- hideKey={node.attrs.hideKey}
- tbox={tbox}
- />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey);
+
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.fontWeight = 'bold';
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.textContent = node.attrs.fieldKey.startsWith('#') ? node.attrs.fieldKey : node.attrs.fieldKey + ' ' + strVal;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ setTimeout(() => ReactDOM.render(<DashFieldViewInternal fieldKey={node.attrs.fieldKey} docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} tbox={tbox} />, this.dom));
+ (this as any).dom = this.dom;
}
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
}
interface IDashFieldViewInternal {
@@ -58,7 +67,6 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
_textBoxDoc: Doc;
_fieldKey: string;
_fieldStringRef = React.createRef<HTMLSpanElement>();
- @observable _showEnumerables: boolean = false;
@observable _dashDoc: Doc | undefined;
constructor(props: IDashFieldViewInternal) {
@@ -67,8 +75,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._textBoxDoc = this.props.tbox.props.Document;
if (this.props.docid) {
- DocServer.GetRefField(this.props.docid).
- then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
} else {
this._dashDoc = this.props.tbox.rootDoc;
}
@@ -77,45 +84,53 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._reactionDisposer?.();
}
- multiValueDelimeter = ";";
+ public static multiValueDelimeter = ';';
+ public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
+ const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === 'PARAMS' ? textBoxDoc[fieldKey] : '');
+ const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
+ return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' };
+ }
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
if (this._dashDoc) {
- const dashVal = this._dashDoc[this._fieldKey] ?? this._dashDoc[DataSym][this._fieldKey] ?? (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
- const fval = dashVal instanceof List ? dashVal.join(this.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
- const boolVal = Cast(fval, "boolean", null);
- const strVal = Field.toString(fval as Field) || "";
-
+ const { boolVal, strVal } = DashFieldViewInternal.fieldContent(this._textBoxDoc, this._dashDoc, this._fieldKey);
// field value is a boolean, so use a checkbox or similar widget to display it
if (boolVal === true || boolVal === false) {
- return <input
- className="dashFieldView-fieldCheck"
- type="checkbox" checked={boolVal}
- onChange={e => {
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
- Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
- }}
- />;
- }
- else // field value is a string, so display it as an editable span
- {
+ return (
+ <input
+ className="dashFieldView-fieldCheck"
+ type="checkbox"
+ checked={boolVal}
+ onChange={e => {
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
+ Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
+ }}
+ />
+ );
+ } // field value is a string, so display it as an editable span
+ else {
// bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
// use React events. Essentially, React events occur after native events have been processed, so corresponding React events
// will never fire because Prosemirror has handled the native events. So we add listeners for native events here.
- return <span className="dashFieldView-fieldSpan" contentEditable={true}
- style={{ display: strVal.length < 2 ? "inline-block" : undefined }}
- suppressContentEditableWarning={true} defaultValue={strVal}
- ref={r => {
- r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
- r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
- r?.addEventListener("pointerdown", action((e) => {
- this._showEnumerables = true;
- e.stopPropagation();
- }));
- }} >
- {strVal}
- </span>;
+ return (
+ <span
+ className="dashFieldView-fieldSpan"
+ contentEditable={true}
+ style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
+ suppressContentEditableWarning={true}
+ defaultValue={strVal}
+ ref={r => {
+ r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r));
+ r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false));
+ r?.addEventListener(
+ 'pointerdown',
+ action(e => e.stopPropagation())
+ );
+ }}>
+ {strVal}
+ </span>
+ );
}
}
}
@@ -123,12 +138,13 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// we need to handle all key events on the input span or else they will propagate to prosemirror.
@action
fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
- if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database.
- e.ctrlKey && DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
+ if (e.key === 'Enter') {
+ // handle the enter key by "submitting" the current text to Dash's database.
this.updateText(span.textContent!, true);
- e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
+ e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view
}
- if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span
+ if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
+ // handle ctrl-A to select all the text within the span
if (window.getSelection) {
const range = document.createRange();
range.selectNodeContents(span);
@@ -137,59 +153,46 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
}
- e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
- }
+ e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
+ };
@action
updateText = (nodeText: string, forceMatch: boolean) => {
- this._showEnumerables = false;
if (nodeText) {
- const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
+ const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText;
// look for a document whose id === the fieldKey being displayed. If there's a match, then that document
// holds the different enumerated values for the field in the titles of its collected documents.
// if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
DocServer.GetRefField(this._fieldKey).then(options => {
- let modText = "";
- (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
+ let modText = '';
+ options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
if (modText) {
// elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
- DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true);
} // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
- else if (nodeText.startsWith(":=")) {
+ else if (nodeText.startsWith(':=')) {
this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
- } else if (nodeText.startsWith("=:=")) {
+ } else if (nodeText.startsWith('=:=')) {
Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
} else {
if (Number(newText).toString() === newText) {
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true);
} else {
- const splits = newText.split(this.multiValueDelimeter);
- if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
+ const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
+ if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
const strVal = splits.length > 1 ? new List<string>(splits) : newText;
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true);
}
}
}
});
}
- }
-
- // display a collection of all the enumerable values for this field
- onPointerDownEnumerables = async (e: any) => {
- e.stopPropagation();
- const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
- collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right");
- }
-
+ };
- // clicking on the label creates a pivot view collection of all documents
- // in the same collection. The pivot field is the fieldKey of this label
- onPointerDownLabelSpan = (e: any) => {
- e.stopPropagation();
+ createPivotForField = (e: React.MouseEvent) => {
let container = this.props.tbox.props.ContainingCollectionView;
while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
container = container.props.ContainingCollectionView;
@@ -201,27 +204,72 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
if (!list) {
alias._columnHeaders = list = new List<SchemaHeaderField>();
}
- list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
- list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
- alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey;
- this.props.tbox.props.addDocTab(alias, "add:right");
+ list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
+ list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
+ alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey;
+ this.props.tbox.props.addDocTab(alias, 'add:right');
}
- }
+ };
+
+ // clicking on the label creates a pivot view collection of all documents
+ // in the same collection. The pivot field is the fieldKey of this label
+ onPointerDownLabelSpan = (e: any) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
+ DashFieldViewMenu.createFieldView = this.createPivotForField;
+ DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16);
+ });
+ };
render() {
- return <div className="dashFieldView" style={{
- width: this.props.width,
- height: this.props.height,
- }}>
- {this.props.hideKey ? (null) :
- <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {this._fieldKey}
- </span>}
+ return (
+ <div
+ className="dashFieldView"
+ style={{
+ width: this.props.width,
+ height: this.props.height,
+ }}>
+ {this.props.hideKey ? null : (
+ <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
+ {this._fieldKey}
+ </span>
+ )}
- {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent}
+ {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+ </div>
+ );
+ }
+}
+@observer
+export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
+ static Instance: DashFieldViewMenu;
+ static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+ constructor(props: any) {
+ super(props);
+ DashFieldViewMenu.Instance = this;
+ }
+ @action
+ showFields = (e: React.MouseEvent) => {
+ DashFieldViewMenu.createFieldView(e);
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
- {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
+ public show = (x: number, y: number) => {
+ this.jumpTo(x, y, true);
+ const hideMenu = () => {
+ this.fadeOut(true);
+ document.removeEventListener('pointerdown', hideMenu);
+ };
+ document.addEventListener('pointerdown', hideMenu);
+ };
+ render() {
+ const buttons = [
+ <Tooltip key="trash" title={<div className="dash-tooltip">{'Remove Link Anchor'}</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.showFields}>
+ <FontAwesomeIcon icon="eye" size="lg" />
+ </button>
+ </Tooltip>,
+ ];
- </div >;
+ return this.getElement(buttons);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 508500ab6..98d611ca6 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,38 +1,38 @@
-import EquationEditor from "equation-editor-react";
-import { IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
+import EquationEditor from 'equation-editor-react';
+import { IReactionDisposer } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../../fields/Doc";
-import { StrCast } from "../../../../fields/Types";
-import "./DashFieldView.scss";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
+import { Doc } from '../../../../fields/Doc';
+import { StrCast } from '../../../../fields/Types';
+import './DashFieldView.scss';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
export class EquationView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
- ReactDOM.render(<EquationViewInternal
- fieldKey={node.attrs.fieldKey}
- width={node.attrs.width}
- height={node.attrs.height}
- setEditor={this.setEditor}
- tbox={tbox}
- />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ ReactDOM.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} setEditor={this.setEditor} tbox={tbox} />, this.dom);
+ (this as any).dom = this.dom;
}
_editor: EquationEditor | undefined;
- setEditor = (editor?: EquationEditor) => this._editor = editor;
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { this._editor?.mathField.focus(); }
- deselectNode() { }
+ setEditor = (editor?: EquationEditor) => (this._editor = editor);
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {
+ this._editor?.mathField.focus();
+ }
+ deselectNode() {}
}
interface IEquationViewInternal {
@@ -56,24 +56,33 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
this._textBoxDoc = this.props.tbox.props.Document;
}
- componentWillUnmount() { this._reactionDisposer?.(); }
- componentDidMount() { this.props.setEditor(this._ref.current ?? undefined); }
+ componentWillUnmount() {
+ this._reactionDisposer?.();
+ }
+ componentDidMount() {
+ this.props.setEditor(this._ref.current ?? undefined);
+ }
render() {
- return <div className="equationView" style={{
- position: "relative",
- display: "inline-block",
- width: this.props.width,
- height: this.props.height,
- bottom: 3,
- }}>
- <EquationEditor ref={this._ref}
- value={StrCast(this._textBoxDoc[this._fieldKey], "y=")}
- onChange={str => this._textBoxDoc[this._fieldKey] = str}
- autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
- autoOperatorNames="sin cos tan"
- spaceBehavesLikeTab={true}
- />
- </div >;
+ return (
+ <div
+ className="equationView"
+ style={{
+ position: 'relative',
+ display: 'inline-block',
+ width: this.props.width,
+ height: this.props.height,
+ bottom: 3,
+ }}>
+ <EquationEditor
+ ref={this._ref}
+ value={StrCast(this._textBoxDoc[this._fieldKey], 'y=')}
+ onChange={str => (this._textBoxDoc[this._fieldKey] = str)}
+ autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
+ autoOperatorNames="sin cos tan"
+ spaceBehavesLikeTab={true}
+ />
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 27817f317..d3d8c47c0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables';
.ProseMirror {
width: 100%;
@@ -11,13 +11,13 @@
}
audiotag {
- left: 0;
- position: absolute;
- cursor: pointer;
- border-radius: 10px;
- width: 10px;
- margin-top: -2px;
- font-size: 4px;
+ left: 0;
+ position: absolute;
+ cursor: pointer;
+ border-radius: 10px;
+ width: 10px;
+ margin-top: -2px;
+ font-size: 4px;
background: lightblue;
}
audiotag:hover {
@@ -63,12 +63,11 @@ audiotag:hover {
.formattedTextBox-outer-selected {
cursor: text;
}
-
+
.formattedTextBox-sidebar-handle {
position: absolute;
top: 0;
- left: 17px;
- //top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views
+ right: 0;
width: 17px;
height: 17px;
font-size: 11px;
@@ -79,15 +78,14 @@ audiotag:hover {
display: flex;
justify-content: center;
align-items: center;
- cursor:grabbing;
+ cursor: grabbing;
box-shadow: $standard-box-shadow;
// transition: 0.2s;
opacity: 0.3;
- &:hover{
+ &:hover {
opacity: 1 !important;
filter: brightness(0.85);
}
-
}
.formattedTextBox-sidebar,
@@ -117,14 +115,15 @@ audiotag:hover {
left: 10%;
}
-.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected,
-.formattedTextBox-inner,
+.formattedTextBox-inner-rounded,
+.formattedTextBox-inner-rounded-selected,
+.formattedTextBox-inner,
.formattedTextBox-inner-minimal,
.formattedTextBox-inner-selected {
height: 100%;
white-space: pre-wrap;
.ProseMirror:hover {
- background: rgba(200,200,200,0.2);
+ background: rgba(200, 200, 200, 0.2);
}
hr {
display: block;
@@ -141,7 +140,7 @@ audiotag:hover {
.formattedTextBox-inner-rounded-selected,
.formattedTextBox-inner-selected {
> .ProseMirror {
- padding:10px;
+ padding: 10px;
}
}
.formattedTextBox-outer-selected {
@@ -236,18 +235,17 @@ footnote::after {
position: absolute;
top: -5px;
left: 27px;
- content: " ";
+ content: ' ';
height: 0;
width: 0;
}
-
.formattedTextBox-inlineComment {
position: relative;
width: 40px;
height: 20px;
&::before {
- content: "→";
+ content: '→';
}
&:hover {
background: orange;
@@ -260,7 +258,7 @@ footnote::after {
width: 40px;
height: 20px;
&::after {
- content: "←";
+ content: '←';
}
}
@@ -270,21 +268,21 @@ footnote::after {
width: 40px;
height: 20px;
&::after {
- content: "...";
+ content: '...';
}
}
.prosemirror-anchor {
- overflow:hidden;
- display:inline-grid;
+ overflow: hidden;
+ display: inline-grid;
}
.prosemirror-linkBtn {
- background:unset;
- color:unset;
- padding:0;
+ background: unset;
+ color: unset;
+ padding: 0;
text-transform: unset;
letter-spacing: unset;
- font-size:unset;
+ font-size: unset;
}
.prosemirror-links {
display: none;
@@ -294,28 +292,28 @@ footnote::after {
z-index: 1;
padding: 5;
border-radius: 2px;
- }
- .prosemirror-hrefoptions{
- width:0px;
- border:unset;
- padding:0px;
- }
-
- .prosemirror-links a {
+}
+.prosemirror-hrefoptions {
+ width: 0px;
+ border: unset;
+ padding: 0px;
+}
+
+.prosemirror-links a {
float: left;
color: white;
text-decoration: none;
border-radius: 3px;
- }
+}
- .prosemirror-links a:hover {
+.prosemirror-links a:hover {
background-color: #eee;
color: black;
- }
+}
- .prosemirror-anchor:hover .prosemirror-links {
+.prosemirror-anchor:hover .prosemirror-links {
display: grid;
- }
+}
.ProseMirror {
padding: 0px;
@@ -334,7 +332,8 @@ footnote::after {
border-left: solid 2px dimgray;
}
- ol, ul {
+ ol,
+ ul {
counter-reset: deci1 0 multi1 0;
padding-left: 1em;
font-family: inherit;
@@ -342,42 +341,231 @@ footnote::after {
ol {
font-family: inherit;
}
- .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; }
+ .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: 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; }
+ .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: 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: 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;}
+ .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: 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) ". "; }
- .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; vertical-align: top; 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; vertical-align: top; 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; 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) ". "; }
+ .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) '. ';
+ }
+ .decimal3:before {
+ transition: 0.5s;
+ counter-increment: deci3;
+ display: inline-block;
+ vertical-align: top;
+ 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;
+ vertical-align: top;
+ 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;
+ 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: -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) ". "; }
+ .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) '. ';
+ }
}
-
@media only screen and (max-width: 1000px) {
- @import "../../global/globalCssVariables";
+ @import '../../global/globalCssVariables';
.ProseMirror {
width: 100%;
@@ -425,7 +613,7 @@ footnote::after {
width: 100%;
height: 100%;
}
-
+
.formattedTextBox-sidebar-handle {
position: absolute;
background: lightgray;
@@ -562,18 +750,17 @@ footnote::after {
position: absolute;
top: -5px;
left: 27px;
- content: " ";
+ content: ' ';
height: 0;
width: 0;
}
-
.formattedTextBox-inlineComment {
position: relative;
width: 40px;
height: 20px;
&::before {
- content: "→";
+ content: '→';
}
&:hover {
background: orange;
@@ -586,7 +773,7 @@ footnote::after {
width: 40px;
height: 20px;
&::after {
- content: "←";
+ content: '←';
}
}
@@ -596,7 +783,7 @@ footnote::after {
width: 40px;
height: 20px;
&::after {
- content: "...";
+ content: '...';
}
}
@@ -606,7 +793,8 @@ footnote::after {
font-family: inherit;
}
- ol, ul {
+ ol,
+ ul {
counter-reset: deci1 0 multi1 0;
padding-left: 1em;
font-family: inherit;
@@ -616,30 +804,191 @@ footnote::after {
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) ". "; }
+ .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 6192b6829..a018f51c2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,89 +1,91 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, reaction, runInAction, observable, trace } from "mobx";
-import { observer } from "mobx-react";
-import { baseKeymap, selectAll } from "prosemirror-commands";
-import { history } from "prosemirror-history";
+import { isEqual } from 'lodash';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+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 { ReplaceStep } from 'prosemirror-transform';
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
+import { keymap } from 'prosemirror-keymap';
+import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
+import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
+import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
-import { RichTextField } from "../../../../fields/RichTextField";
+import { RichTextField } from '../../../../fields/RichTextField';
import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { Cast, DateCast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { ComputedField } from '../../../../fields/ScriptField';
+import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
-import { DocServer } from "../../../DocServer";
+import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
-import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from "../../../util/DragManager";
-import { makeTemplate } from '../../../util/DropConverter';
-import { SelectionManager } from "../../../util/SelectionManager";
+import { DragManager } from '../../../util/DragManager';
+import { MakeTemplate } from '../../../util/DropConverter';
+import { LinkManager } from '../../../util/LinkManager';
+import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from "../../DocComponent";
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { DocumentButtonBar } from '../../DocumentButtonBar';
+import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
+import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
-import { AudioBox } from '../AudioBox';
-import { FieldView, FieldViewProps } from "../FieldView";
+import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
-import { DashDocCommentView } from "./DashDocCommentView";
-import { DashDocView } from "./DashDocView";
-import { DashFieldView } from "./DashFieldView";
-import { EquationView } from "./EquationView";
-import { FootnoteView } from "./FootnoteView";
-import "./FormattedTextBox.scss";
+import { DashDocCommentView } from './DashDocCommentView';
+import { DashDocView } from './DashDocView';
+import { DashFieldView } from './DashFieldView';
+import { EquationView } from './EquationView';
+import { FootnoteView } from './FootnoteView';
+import './FormattedTextBox.scss';
import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment';
-import { OrderedListView } from "./OrderedListView";
-import { buildKeymap, updateBullets } from "./ProsemirrorExampleTransfer";
-import { removeMarkWithAttrs } from "./prosemirrorPatches";
+import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer';
+import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
-import { RichTextRules } from "./RichTextRules";
-import { schema } from "./schema_rts";
-import { SummaryView } from "./SummaryView";
-import applyDevTools = require("prosemirror-dev-tools");
-import React = require("react");
-import { SidebarAnnos } from '../../SidebarAnnos';
-import { Colors } from '../../global/globalEnums';
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-const translateGoogleApi = require("translate-google-api");
+import { RichTextRules } from './RichTextRules';
+import { schema } from './schema_rts';
+import { SummaryView } from './SummaryView';
+import applyDevTools = require('prosemirror-dev-tools');
+import React = require('react');
+import { text } from 'body-parser';
+import { CollectionTreeView } from '../../collections/CollectionTreeView';
+const translateGoogleApi = require('translate-google-api');
export interface FormattedTextBoxProps {
- makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
- xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
+ makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
+ xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yPadding?: number;
noSidebar?: boolean;
dontScale?: boolean;
dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
}
-export const GoogleRef = "googleDocId";
+export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() {
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(FormattedTextBox, fieldStr);
+ }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
- static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
+ static _globalHighlights: string[] = ['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items'];
static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
@@ -93,7 +95,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
- private _applyingChange: string = "";
+ private _applyingChange: string = '';
private _searchIndex = 0;
private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
@@ -102,7 +104,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _dropDisposer?: DragManager.DragDropDisposer;
private _recordingStart: number = 0;
private _ignoreScroll = false;
- private _lastText = "";
+ private _lastText = '';
private _focusSpeed: Opt<number>;
private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
@@ -113,21 +115,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _downY = 0;
private _break = true;
public ProseRef?: HTMLDivElement;
- public get EditorView() { return this._editorView; }
- public get SidebarKey() { return this.fieldKey + "-sidebar"; }
- @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); }
-
- @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
- @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
- @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; }
- @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
- @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
- @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
- @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
- @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); }
- @computed get _recording() { return this.dataDoc?.mediaState === "recording"; }
+ public get EditorView() {
+ return this._editorView;
+ }
+ public get SidebarKey() {
+ return this.fieldKey + '-sidebar';
+ }
+ @computed get allSidebarDocs() {
+ return DocListCast(this.dataDoc[this.SidebarKey]);
+ }
+
+ @computed get noSidebar() {
+ return this.props.docViewPath?.()[this.props.docViewPath().length - 2]?.rootDoc.type === DocumentType.RTF || this.props.noSidebar || this.Document._noSidebar;
+ }
+ @computed get sidebarWidthPercent() {
+ return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%');
+ }
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4'));
+ }
+ @computed get autoHeight() {
+ return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight;
+ }
+ @computed get textHeight() {
+ return NumCast(this.rootDoc[this.fieldKey + '-height']);
+ }
+ @computed get scrollHeight() {
+ return NumCast(this.rootDoc[this.fieldKey + '-scrollHeight']);
+ }
+ @computed get sidebarHeight() {
+ return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '-height']);
+ }
+ @computed get titleHeight() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ }
+ @computed get autoHeightMargins() {
+ return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins);
+ }
+ @computed get _recording() {
+ return this.dataDoc?.mediaState === 'recording';
+ }
set _recording(value) {
- !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined);
+ !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this.props);
@@ -140,28 +169,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
history(),
keymap(this._keymap),
keymap(baseKeymap),
- new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }),
- new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } })
- ]
+ new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
+ new Plugin({
+ view(editorView) {
+ return new FormattedTextBoxComment(editorView);
+ },
+ }),
+ ],
};
}
public static PasteOnLoad: ClipboardEvent | undefined;
- public static SelectOnLoad = "";
+ public static SelectOnLoad = '';
public static DontSelectInitialText = false; // whether initial text should be selected or not
- public static SelectOnLoadChar = "";
- public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; }
+ public static SelectOnLoadChar = '';
+ public static IsFragment(html: string) {
+ return html.indexOf('data-pm-slice') !== -1;
+ }
public static GetHref(html: string): string {
const parser = new DOMParser();
const parsedHtml = parser.parseFromString(html, 'text/html');
- if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 &&
- (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
+ if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
}
- return "";
+ return '';
}
public static GetDocFromUrl(url: string) {
- return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid
+ return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid
}
constructor(props: any) {
@@ -172,7 +206,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
// 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.
+ // 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) {
this.unhighlightSearchTerms();
@@ -195,9 +229,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- // removes all the specified link references from the selection.
+ // removes all the specified link references 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 RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) {
+ public RemoveAnchorFromSelection(allAnchors: { 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, { allAnchors }));
@@ -205,11 +239,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
- getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection");
+ getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection');
@action
setupAnchorMenu = () => {
- AnchorMenu.Instance.Status = "marquee";
+ AnchorMenu.Instance.Status = 'marquee';
AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
!this.layoutDoc.showSidebar && this.toggleSidebar();
@@ -220,15 +254,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return undefined;
});
AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
+ AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
- * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
+ * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
*/
AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
e.preventDefault();
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
- const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);
FormattedTextBox.SelectOnLoad = target[Id];
return target;
};
@@ -237,56 +272,56 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
- }
+ };
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- const tsel = this._editorView.state.selection.$from;
- tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
- const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
- const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box
- const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
+ const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+ const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box
+ const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
const json = JSON.stringify(state.toJSON());
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
- const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ?
- json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
+ const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || effectiveAcl === AclSelfEdit) {
+ if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
const accumTags = [] as string[];
state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
- if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) {
+ if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
accumTags.push(node.attrs.fieldKey);
}
});
- const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#"));
+ const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith('#'));
const added = accumTags.filter(tag => !curTags.includes(tag));
const removed = curTags.filter(tag => !accumTags.includes(tag));
- removed.forEach(r => this.dataDoc[r] = undefined);
- added.forEach(a => this.dataDoc[a] = a);
+ removed.forEach(r => (this.dataDoc[r] = undefined));
+ added.forEach(a => (this.dataDoc[a] = a));
let unchanged = true;
if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
this._applyingChange = this.fieldKey;
- (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
- if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+ curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text && (this.dataDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now())));
+ if ((!curTemp && !curProto) || curText || json.includes('dash')) {
+ // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ this.dataDoc[this.props.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
- } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+ } else {
+ // if we've deleted all the text in a note driven by a template, then restore the template data
this.dataDoc[this.props.fieldKey] = undefined;
this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+ this.dataDoc[this.props.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have
+ ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
- this._applyingChange = "";
+ this._applyingChange = '';
if (!unchanged) {
this.updateTitle();
this.tryUpdateScrollHeight();
@@ -304,16 +339,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
AnchorMenu.Instance.fadeOut(true);
}
}
- }
+ };
- // for inserting timestamps
+ // for inserting timestamps
insertTime = () => {
let linkTime;
let linkAnchor;
let link;
DocListCast(this.dataDoc.links).forEach((l, i) => {
- const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === "recording") {
+ const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
+ if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
link = l;
@@ -344,40 +379,85 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
}
}
- }
+ };
+
+ autoLink = () => {
+ const newAutoLinks = new Set<Doc>();
+ const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
+ if (this._editorView?.state.doc.textContent) {
+ const f = this._editorView.state.selection.from;
+ const t = this._editorView.state.selection.to;
+ var tr = this._editorView.state.tr as any;
+ const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
+ tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
+ DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+ this._editorView?.dispatch(tr);
+ }
+ oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
+ };
updateTitle = () => {
- if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
- StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
- (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
+ const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
+ if (
+ !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ (title.startsWith('-') || title.startsWith('@')) &&
+ this._editorView &&
+ !this.dataDoc['title-custom'] &&
+ (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text')
+ ) {
let node = this._editorView.state.doc;
- while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
+ while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
- this.dataDoc.title = "-" + str.substr(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : "");
+ const prefix = str.startsWith('@') ? '' : '-';
+
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
+ if (!(cfield instanceof ComputedField)) {
+ this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '');
+ if (str.startsWith('@') && str.length > 1) {
+ Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc);
+ }
+ }
}
- }
+ };
- // needs a better API for taking in a set of words with target documents instead of just one target
- hyperlinkTerms = (terms: string[], target: Doc) => {
- if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
- const res1 = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
- let tr = this._editorView.state.tr;
- const flattened1: TextSelection[] = [];
- res1.map(r => r.map(h => flattened1.push(h)));
+ // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
+ hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
+ const editorView = this._editorView;
+ if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
+ const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
+ const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm);
+ var alink: Doc | undefined;
flattened1.forEach((flat, i) => {
- const flattened: TextSelection[] = [];
- const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
- res.map(r => r.map(h => flattened.push(h)));
+ const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
- const anchor = Docs.Create.TextanchorDocument();
- const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
- const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }];
- const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location });
- tr = tr.addMark(flattened[i].from, flattened[i].to, link);
+ const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
+ const sel = flattened[i];
+ tr = tr.addMark(sel.from, sel.to, splitter);
+ 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.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+ alink =
+ alink ??
+ (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
+ DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
+ newAutoLinks.add(alink);
+ const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
+ allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
+ const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
+ tr = tr.addMark(pos, pos + node.nodeSize, link);
+ }
+ });
+ tr = tr.removeMark(sel.from, sel.to, splitter);
});
- this._editorView.dispatch(tr);
}
- }
+ return tr;
+ };
+ @action
+ search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
+ if (clear) this.unhighlightSearchTerms();
+ else this.highlightSearchTerms([searchString], bwd!);
+ return true;
+ };
highlightSearchTerms = (terms: string[], backward: boolean) => {
if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
@@ -391,21 +471,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (backward === true) {
if (this._searchIndex > 1) {
this._searchIndex += -2;
- }
- else if (this._searchIndex === 1) {
+ } else if (this._searchIndex === 1) {
this._searchIndex = length - 1;
- }
- else if (this._searchIndex === 0 && length !== 1) {
+ } else if (this._searchIndex === 0 && length !== 1) {
this._searchIndex = length - 2;
}
-
}
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
- flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
+ flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)));
flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
}
- }
+ };
unhighlightSearchTerms = () => {
if (window.screen.width < 600) null;
@@ -414,20 +491,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
const end = this._editorView.state.doc.nodeSize - 2;
this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
-
}
if (FormattedTextBox.PasteOnLoad) {
- const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin");
- const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfRegion");
+ const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin');
+ const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion');
FormattedTextBox.PasteOnLoad = undefined;
setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10);
}
- }
+ };
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
- }
+ };
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
this.ProseRef = ele;
@@ -435,8 +511,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.setupEditor(this.config, this.props.fieldKey);
this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc);
}
- // if (this.autoHeight) this.tryUpdateScrollHeight();
- }
+ // if (this.autoHeight) this.tryUpdateScrollHeight();
+ };
@undoBatch
@action
@@ -454,22 +530,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// embed document when dragg marked as embed
} else if (de.embedKey) {
const target = dragData.droppedDocuments[0];
- target._fitToBox = true;
+ target._fitContentsToBox = true;
const node = schema.nodes.dashDoc.create({
width: target[WidthSym](),
height: target[HeightSym](),
- title: "dashDoc",
+ title: 'dashDoc',
docid: target[Id],
- float: "unset"
+ float: 'unset',
});
const view = this._editorView!;
view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
e.stopPropagation();
} // otherwise, fall through to outer collection to handle drop
}
- }
+ };
- getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
+ getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null {
let offset = 0;
if (context === node) return { from: offset, to: offset + node.nodeSize };
@@ -480,8 +556,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const result = this.getNodeEndpoints((context.content as any).content[i], node);
if (result) {
return {
- from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
- to: result.to + offset + (context.type.name === "doc" ? 0 : 1)
+ from: result.from + offset + (context.type.name === 'doc' ? 0 : 1),
+ to: result.to + offset + (context.type.name === 'doc' ? 0 : 1),
};
}
offset += (context.content as any).content[i].nodeSize;
@@ -497,9 +573,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let ret: TextSelection[] = [];
if (node.isTextblock) {
- let index = 0, foundAt;
+ let index = 0,
+ foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
- const regexp = new RegExp(find.replace("*", ""), "i");
+ const regexp = new RegExp(find.replace('*', ''), 'i');
if (regexp) {
while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
@@ -508,165 +585,247 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
} else {
- node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
+ node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));
}
return ret;
}
updateHighlights = () => {
+ const highlights = FormattedTextBox._globalHighlights;
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, "");
+ if (highlights.indexOf('Audio Tags') === -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
+ }
+ if (highlights.indexOf('Text from Others') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
- if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
+ if (highlights.indexOf('My Text') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });
}
- if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
+ if (highlights.indexOf('Todo Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'todo', { outline: 'black solid 1px' });
}
- if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });
+ if (highlights.indexOf('Important Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'important', { 'font-size': 'larger' });
}
- if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });
+ if (highlights.indexOf('Bold Text') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, '');
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
}
- if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });
+ if (highlights.indexOf('Disagree Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'disagree', { 'text-decoration': 'line-through' });
}
- if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });
+ if (highlights.indexOf('Ignore Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'ignore', { 'font-size': '1' });
}
- if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ if (highlights.indexOf('By Recent Minute') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
const min = Math.round(Date.now() / 1000 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
setTimeout(this.updateHighlights);
}
- if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ if (highlights.indexOf('By Recent Hour') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
- }
+ };
@observable _showSidebar = false;
- @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ @computed get SidebarShown() {
+ return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
+ }
@action
toggleSidebar = (preview: boolean = false) => {
- const prevWidth = this.sidebarWidth();
+ const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
if (preview) this._showSidebar = true;
- else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
+ else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
- this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
- }
+ this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
+ };
sidebarDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
- }
+ const batch = UndoManager.StartBatch('sidebar');
+ setupMoveUpEvents(
+ this,
+ e,
+ this.sidebarMove,
+ (e, movement, isClick) => !isClick && batch.end(),
+ () => {
+ this.toggleSidebar();
+ batch.end();
+ },
+ true
+ );
+ };
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const bounds = this._ref.current!.getBoundingClientRect();
- this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
- this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+ const localDelta = this.props
+ .ScreenToLocalTransform()
+ .scale(this.props.NativeDimScaling?.() || 1)
+ .transformDirection(delta[0], delta[1]);
+ const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100;
+ const width = this.layoutDoc[WidthSym]() + localDelta[0];
+ this.layoutDoc._sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
+ this.layoutDoc.width = width;
+ this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%';
e.preventDefault();
return false;
- }
+ };
+
+ @undoBatch
+ deleteAnnotation = (anchor: Doc) => {
+ LinkManager.Instance.deleteLink(DocListCast(anchor.links)[0]);
+ // const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]);
+ // this.props.dataDoc[this.props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
+ // AnchorMenu.Instance.fadeOut(true);
+ this.props.select(false);
+ };
+
+ @undoBatch
+ pinToPres = (anchor: Doc) => this.props.pinToPres(anchor);
+
+ @undoBatch
+ makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin);
+
+ isPushpin = (anchor: Doc) => BoolCast(anchor.isPushpin);
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
+ const editor = this._editorView!;
+ const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
+ let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
+ while (target && !target.dataset?.targethrefs) target = target.parentElement;
+ if (target) {
+ const hrefs = (target.dataset?.targethrefs as string)
+ ?.trim()
+ .split(' ')
+ .filter(h => h);
+ const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0];
+ e.persist();
+ anchorDoc &&
+ DocServer.GetRefField(anchorDoc).then(
+ action(anchor => {
+ AnchorMenu.Instance.Status = 'annotation';
+ AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc);
+ AnchorMenu.Instance.Pinned = false;
+ AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc);
+ AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc);
+ AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc);
+ AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
+ })
+ );
+ e.stopPropagation();
+ return;
+ }
+
const changeItems: ContextMenuProps[] = [];
changeItems.push({
- description: "plain", event: undoBatch(() => {
+ description: 'plain',
+ event: undoBatch(() => {
Doc.setNativeView(this.rootDoc);
this.layoutDoc.autoHeightMargins = undefined;
- }), icon: "eye"
+ }),
+ icon: 'eye',
});
changeItems.push({
- description: "metadata", event: undoBatch(() => {
+ description: 'metadata',
+ event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
- this.rootDoc.layoutKey = "layout_meta";
- setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50);
- }), icon: "eye"
+ this.rootDoc.layoutKey = 'layout_meta';
+ setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50), 50);
+ }),
+ icon: 'eye',
});
- const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
+ const noteTypesDoc = Cast(Doc.UserDoc()['template-notes'], Doc, null);
DocListCast(noteTypesDoc?.data).forEach(note => {
const icon: IconProp = StrCast(note.icon) as IconProp;
changeItems.push({
- description: StrCast(note.title), event: undoBatch(() => {
+ description: StrCast(note.title),
+ event: undoBatch(() => {
this.layoutDoc.autoHeightMargins = undefined;
Doc.setNativeView(this.rootDoc);
DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
- }), icon: icon
+ }),
+ icon: icon,
});
});
- !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
const highlighting: ContextMenuProps[] = [];
- const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others"];
- const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"];
- (Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
+ const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text'];
+ const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
+ (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
highlighting.push({
- description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option,
+ event: () => {
e.stopPropagation();
- if (FormattedTextBox._highlights.indexOf(option) === -1) {
- FormattedTextBox._highlights.push(option);
+ if (FormattedTextBox._globalHighlights.indexOf(option) === -1) {
+ FormattedTextBox._globalHighlights.push(option);
} else {
- FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+ FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);
}
+ runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join('')));
this.updateHighlights();
- }, icon: "expand-arrows-alt"
- }));
+ },
+ icon: 'expand-arrows-alt',
+ })
+ );
const uicontrols: ContextMenuProps[] = [];
- !Doc.UserDoc().noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" });
- uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" });
- uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
- uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
- !Doc.UserDoc().noviceMode && uicontrols.push({
- description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
- proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
- });
- cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+ !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
+ !Doc.noviceMode &&
+ uicontrols.push({
+ description: 'Broadcast Message',
+ event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)),
+ icon: 'expand-arrows-alt',
+ });
+ cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' });
- const appearance = cm.findByDescription("Appearance...");
- const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
- appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
+ const appearance = cm.findByDescription('Appearance...');
+ const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
+ appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
// this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
- !Doc.UserDoc().noviceMode && appearanceItems.push({
- description: "Make Default Layout", event: () => {
- if (!this.layoutDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = "text";
- this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
- } else if (!this.rootDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = "text";
- this.rootDoc.layout = this.layoutDoc.layout as string;
- this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
- this.rootDoc.isTemplateDoc = false;
- this.rootDoc.isTemplateForField = "";
- this.rootDoc.layoutKey = "layout";
- this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
- setTimeout(() => {
- this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height
- this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template
- this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
- this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
- this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null);
- }, 10);
- }
- Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
- Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
- }, icon: "eye"
- });
- cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+ !Doc.noviceMode &&
+ appearanceItems.push({
+ description: 'Make Default Layout',
+ event: () => {
+ if (!this.layoutDoc.isTemplateDoc) {
+ const title = StrCast(this.rootDoc.title);
+ this.rootDoc.title = 'text';
+ MakeTemplate(this.rootDoc, true, title);
+ } else if (!this.rootDoc.isTemplateDoc) {
+ const title = StrCast(this.rootDoc.title);
+ this.rootDoc.title = 'text';
+ this.rootDoc.layout = this.layoutDoc.layout as string;
+ this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
+ this.rootDoc.isTemplateDoc = false;
+ this.rootDoc.isTemplateForField = '';
+ this.rootDoc.layoutKey = 'layout';
+ MakeTemplate(this.rootDoc, true, title);
+ setTimeout(() => {
+ this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height
+ this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template
+ this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
+ this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null);
+ this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, 'string', null);
+ }, 10);
+ }
+ Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
+ Doc.AddDocToList(Cast(Doc.UserDoc()['template-notes'], Doc, null), 'data', this.rootDoc);
+ },
+ icon: 'eye',
+ });
+ cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
- const options = cm.findByDescription("Options...");
- const optionItems = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
- optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+ const options = cm.findByDescription('Options...');
+ const optionItems = options && 'subitems' in options ? options.subitems : [];
+ optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' });
+ optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
- }
+ };
breakupDictation = () => {
if (this._editorView && this._recording) {
@@ -675,12 +834,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const state = this._editorView.state;
const to = state.selection.to;
const updated = TextSelection.create(state.doc, to, to);
- this._editorView.dispatch(state.tr.setSelection(updated).insertText("\n", to));
+ this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));
if (this._recording) {
this.recordDictation();
}
}
- }
+ };
recordDictation = () => {
DictationManager.Controls.listen({
interimHandler: this.setDictationContent,
@@ -690,26 +849,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DictationManager.Controls.stop();
}
});
- }
+ };
stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
setDictationContent = (value: string) => {
if (this._editorView && this._recordingStart) {
if (this._break) {
- const textanchor = Docs.Create.TextanchorDocument({ title: "dictation anchor" });
+ const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
this.addDocument(textanchor);
const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement();
link && (Doc.GetProto(link).isDictation = true);
if (!link) return;
const audioanchor = Cast(link.anchor2, Doc, null);
if (!audioanchor) return;
- audioanchor.backgroundColor = "tan";
+ audioanchor.backgroundColor = 'tan';
const audiotag = this._editorView.state.schema.nodes.audiotag.create({
timeCode: NumCast(audioanchor._timecodeToShow),
audioId: audioanchor[Id],
- textId: textanchor[Id]
+ textId: textanchor[Id],
});
- Doc.GetProto(textanchor).title = "dictation:" + audiotag.attrs.timeCode;
+ Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -719,8 +878,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const tr = this._editorView.state.tr.insertText(value);
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
}
- }
+ };
+ // TODO: nda -- Look at how link anchors are added
makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
const state = this._editorView?.state;
if (state) {
@@ -728,7 +888,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
- const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true });
+ const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
@@ -739,7 +899,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
tr = tr.addMark(pos, pos + node.nodeSize, link);
}
});
- this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
+ this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
return anchor;
@@ -750,8 +910,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
scrollFocus = (textAnchor: Doc, smooth: boolean) => {
- if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) {
+ let didToggle = false;
+ if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {
this.toggleSidebar(!smooth);
+ didToggle = true;
}
const textAnchorId = textAnchor[Id];
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
@@ -780,10 +942,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
- return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;
};
let start = 0;
+ this._didScroll = false; // assume we don't need to scroll. if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
if (this._editorView && textAnchorId) {
const editor = this._editorView;
const ret = findAnchorFrag(editor.state.doc.content, editor);
@@ -797,57 +960,72 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId;
- addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow", transform: "scale(3)", "transform-origin": "left bottom" });
- setTimeout(() => this._focusSpeed = undefined, this._focusSpeed);
+ addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
+ setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
}
}
- return this._focusSpeed;
- }
+ return this._didScroll ? this._focusSpeed : didToggle ? 1 : undefined; // if we actually scrolled, then return some focusSpeed
+ };
+ getScrollHeight = () => this.scrollHeight;
// if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc.
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.rootDoc[this.fieldKey + "-height"] = scrollHeight;
+ this.rootDoc[this.fieldKey + '-height'] = scrollHeight;
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
- }
+ };
+ @computed get contentScaling() {
+ return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.NativeDimScaling?.() || 1 : 1;
+ }
componentDidMount() {
!this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._cachedLinks = DocListCast(this.Document.links);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
- this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight());
- this._disposers.scrollHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
+ this._disposers.autoHeight = reaction(
+ () => this.autoHeight,
+ autoHeight => autoHeight && this.tryUpdateScrollHeight()
+ );
+ this._disposers.scrollHeight = reaction(
+ () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
({ width, scrollHeight, autoHeight }) => {
width && autoHeight && this.resetNativeHeight(scrollHeight);
- }, { fireImmediately: true }
+ },
+ { fireImmediately: true }
);
- this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on
+ this._disposers.componentHeights = reaction(
+ // set the document height when one of the component heights changes and autoHeight is on
() => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
- autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight));
- }, { fireImmediately: true });
- 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
+ const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
+ if (autoHeight && newHeight && newHeight !== this.rootDoc.height) {
+ this.props.setHeight?.(newHeight);
+ }
+ },
+ { fireImmediately: true }
+ );
+ this._disposers.links = reaction(
+ () => DocListCast(this.dataDoc.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 => {
if (instance) {
this.pullFromGoogleDoc(this.checkState);
- this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true);
+ this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true));
}
}
);
this._disposers.editorState = reaction(
() => {
- const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined :
- this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ?
- this.dataDoc : this.layoutDoc;
+ const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : this.dataDoc?.[this.props.fieldKey + '-noTemplate'] || !this.layoutDoc[this.props.fieldKey] ? this.dataDoc : this.layoutDoc;
return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) };
},
incomingValue => {
@@ -862,7 +1040,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
}
}
- },
+ }
);
this._disposers.pullDoc = reaction(
() => this.props.Document[Pulls],
@@ -883,33 +1061,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
);
- this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc),
- search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(),
- { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
+ this._disposers.search = reaction(
+ () => Doc.IsSearchMatch(this.rootDoc),
+ search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
+ { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }
+ );
- this._disposers.selected = reaction(() => this.props.isSelected(),
+ this._disposers.selected = reaction(
+ () => this.props.isSelected(),
action(selected => {
+ this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : '';
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
if (this._editorView && selected) {
RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+ this.autoLink();
}
- }), { fireImmediately: true });
+ }),
+ { fireImmediately: true }
+ );
if (!this.props.dontRegisterView) {
- this._disposers.record = reaction(() => this._recording,
+ this._disposers.record = reaction(
+ () => this._recording,
() => {
this.stopDictation(true);
if (this._recording) {
this.recordDictation();
}
- },
+ }
);
if (this._recording) setTimeout(this.recordDictation);
}
- var quickScroll: string | undefined = "";
- this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
+ var quickScroll: string | undefined = '';
+ this._disposers.scroll = reaction(
+ () => NumCast(this.layoutDoc._scrollTop),
pos => {
if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
@@ -922,17 +1109,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._scrollRef.current.scrollTo({ top: pos });
}
}
- }, { fireImmediately: true }
+ },
+ { fireImmediately: true }
);
quickScroll = undefined;
- setTimeout(this.tryUpdateScrollHeight, 10);
+ this.tryUpdateScrollHeight();
}
pushToGoogleDoc = async () => {
this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
const modes = GoogleApiClientUtils.Docs.WriteMode;
let mode = modes.Replace;
- let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+ let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], 'string');
if (!reference) {
mode = modes.Insert;
reference = { title: StrCast(this.dataDoc.title) };
@@ -942,7 +1130,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
response && (this.dataDoc[GoogleRef] = response.documentId);
- const pushSuccess = response !== undefined && !("errors" in response);
+ const pushSuccess = response !== undefined && !('errors' in response);
dataDoc.googleDocUnchanged = pushSuccess;
DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
}
@@ -951,15 +1139,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (exportState && reference) {
const content: GoogleApiClientUtils.Docs.Content = {
text: exportState.text,
- requests: []
+ requests: [],
};
GoogleApiClientUtils.Docs.write({ reference, content, mode });
}
};
- UndoManager.AddEvent({ undo, redo, prop: "" });
+ UndoManager.AddEvent({ undo, redo, prop: '' });
redo();
});
- }
+ };
pullFromGoogleDoc = async (handler: PullHandler) => {
const dataDoc = this.dataDoc;
@@ -969,7 +1157,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
}
exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
- }
+ };
updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
let pullSuccess = false;
@@ -984,13 +1172,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}, 0);
dataDoc.title = exportState.title;
- this.dataDoc["title-custom"] = true;
+ this.dataDoc['title-custom'] = true;
dataDoc.googleDocUnchanged = true;
} else {
delete dataDoc[GoogleRef];
}
DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
- }
+ };
checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
if (exportState && this._editorView) {
@@ -1000,56 +1188,61 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
dataDoc.googleDocUnchanged = unchanged;
DocumentButtonBar.Instance.setPullState(unchanged);
}
- }
+ };
clipboardTextSerializer = (slice: Slice): string => {
- let text = "", separated = true;
- const from = 0, to = slice.content.size;
- slice.content.nodesBetween(from, to, (node, pos) => {
- if (node.isText) {
- text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
- separated = false;
- } else if (!separated && node.isBlock) {
- text += "\n";
- separated = true;
- } else if (node.type.name === "hard_break") {
- text += "\n";
- }
- }, 0);
+ let text = '',
+ separated = true;
+ const from = 0,
+ to = slice.content.size;
+ slice.content.nodesBetween(
+ from,
+ to,
+ (node, pos) => {
+ if (node.isText) {
+ text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
+ separated = false;
+ } else if (!separated && node.isBlock) {
+ text += '\n';
+ separated = true;
+ } else if (node.type.name === 'hard_break') {
+ text += '\n';
+ }
+ },
+ 0
+ );
return text;
- }
+ };
handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
const cbe = event as ClipboardEvent;
- const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin");
- const pdfRegionId = cbe.clipboardData?.getData("dash/pdfRegion");
+ const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin');
+ const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion');
return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false;
- }
+ };
addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => {
const view = this._editorView!;
if (pdfDocId && pdfRegionId) {
DocServer.GetRefField(pdfDocId).then(pdfDoc => {
DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
- if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
+ if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) {
setTimeout(async () => {
const targetField = Doc.LayoutFieldKey(pdfDoc);
- const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
+ const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations
if (targetAnnotations) targetAnnotations.push(pdfRegion);
- else Doc.AddDocToList(pdfDoc[DataSym], targetField + "-annotations", pdfRegion);
+ else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion);
});
- const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted");
+ const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted');
if (link) {
const linkId = link[Id];
- const quote = view.state.schema.nodes.blockquote.create();
- quote.content = addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+ const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) });
const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0);
if (slice) {
- view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
+ view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
} else {
selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView()));
-
}
}
}
@@ -1059,31 +1252,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
return false;
-
function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
const nodes: Node[] = [];
frag.forEach(node => nodes.push(marker(node)));
return Fragment.fromArray(nodes);
}
-
function addLinkMark(node: Node, title: string, linkId: string) {
if (!node.isText) {
const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
return node.copy(content);
}
const marks = [...node.marks];
- const linkIndex = marks.findIndex(mark => mark.type.name === "link");
+ const linkIndex = marks.findIndex(mark => mark.type.name === 'link');
const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
- const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
+ const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
- }
+ };
isActiveTab(el: Element | null | undefined) {
while (el && el !== document.body) {
- if (getComputedStyle(el).display === "none") return false;
+ if (getComputedStyle(el).display === 'none') return false;
el = el.parentNode as any;
}
return true;
@@ -1095,9 +1286,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
view(newView) {
runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
return new RichTextMenuPlugin({ editorProps: this.props });
- }
+ },
});
}
+ _didScroll = false;
setupEditor(config: any, fieldKey: string) {
const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.props.fieldKey]);
const rtfField = Cast((!curText && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
@@ -1106,13 +1298,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView?.destroy();
this._editorView = new EditorView(this.ProseRef, {
state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
- handleScrollToSelection: (editorView) => {
+ handleScrollToSelection: editorView => {
const docPos = editorView.coordsAtPos(editorView.state.selection.to);
const viewRect = self._ref.current!.getBoundingClientRect();
const scrollRef = self._scrollRef.current;
const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
- if ((topOff || botOff) && scrollRef) {
+ if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
if (this._focusSpeed !== undefined) {
@@ -1120,18 +1312,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
} else {
scrollRef.scrollTo({ top: scrollPos });
}
+ this._didScroll = true;
}
return true;
},
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); },
- dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); },
- dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); },
- equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); },
- summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); },
- ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
- footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }
+ dashComment(node: any, view: any, getPos: any) {
+ return new DashDocCommentView(node, view, getPos);
+ },
+ dashDoc(node: any, view: any, getPos: any) {
+ return new DashDocView(node, view, getPos, self);
+ },
+ dashField(node: any, view: any, getPos: any) {
+ return new DashFieldView(node, view, getPos, self);
+ },
+ equation(node: any, view: any, getPos: any) {
+ return new EquationView(node, view, getPos, self);
+ },
+ summary(node: any, view: any, getPos: any) {
+ return new SummaryView(node, view, getPos);
+ },
+ //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
+ footnote(node: any, view: any, getPos: any) {
+ return new FootnoteView(node, view, getPos);
+ },
},
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
@@ -1142,9 +1347,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (startupText) {
dispatch(state.tr.insertText(startupText));
}
- const textAlign = StrCast(this.dataDoc["text-align"], StrCast(Doc.UserDoc().textAlign, "left"));
- if (textAlign !== "left") {
- selectAll(this._editorView.state, (tr) => {
+ const textAlign = StrCast(this.dataDoc['text-align'], StrCast(Doc.UserDoc().textAlign, 'left'));
+ if (textAlign !== 'left') {
+ selectAll(this._editorView.state, tr => {
this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign })));
});
}
@@ -1154,33 +1359,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
- FormattedTextBox.SelectOnLoad = "";
+ const selLoadChar = FormattedTextBox.SelectOnLoadChar;
+ FormattedTextBox.SelectOnLoad = '';
this.props.select(false);
- if (FormattedTextBox.SelectOnLoadChar && this._editorView) {
+ if (selLoadChar && this._editorView) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
- const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks);
+ const tr = this._editorView.state.tr
+ .setStoredMarks(storedMarks)
+ .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size)
+ .setStoredMarks(storedMarks);
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
- FormattedTextBox.SelectOnLoadChar = "";
- } else if (curText && !FormattedTextBox.DontSelectInitialText) {
- selectAll(this._editorView!.state, this._editorView?.dispatch);
+ } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) {
+ selectAll(this._editorView.state, this._editorView?.dispatch);
this.startUndoTypingBatch();
+ } else if (this._editorView) {
+ this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
FormattedTextBox.DontSelectInitialText = false;
}
selectOnLoad && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
- this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []),
- schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
- ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
- ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []),
- ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
- ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])];
+ this._editorView.state.storedMarks = [
+ ...(this._editorView.state.storedMarks ?? []),
+ schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
+ ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
+ ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
+ ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
+ ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
+ ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
+ ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
+ ];
}
}
@@ -1191,12 +1403,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.unhighlightSearchTerms();
this._editorView?.destroy();
RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+ FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none');
}
onPointerDown = (e: React.PointerEvent): void => {
+ if ((e.nativeEvent as any).handledByInnerReactInstance) {
+ return e.stopPropagation();
+ } else (e.nativeEvent as any).handledByInnerReactInstance = true;
+
+ if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
- if ((e.target as any).tagName === "AUDIOTAG") {
+ if ((e.target as any).tagName === 'AUDIOTAG') {
e.preventDefault();
e.stopPropagation();
const timecode = Number((e.target as any)?.dataset?.timecode);
@@ -1207,10 +1424,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const func = () => {
const docView = DocumentManager.Instance.getDocumentView(audiodoc);
if (!docView) {
- this.props.addDocTab(audiodoc, "add:bottom");
+ this.props.addDocTab(audiodoc, 'add:bottom');
setTimeout(func);
- }
- else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that
+ } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
};
func();
}
@@ -1226,50 +1442,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._downEvent = true;
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
+ // stop propagation if not in sidebar
// bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView,
// but that's changed, so this shouldn't be needed.
//e.stopPropagation(); // if the text box is selected, then it consumes all down events
- document.addEventListener("pointerup", this.onSelectEnd);
- document.addEventListener("pointermove", this.onSelectMove);
+ document.addEventListener('pointerup', this.onSelectEnd);
+ document.addEventListener('pointermove', this.onSelectMove);
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
- }
+ };
onSelectMove = (e: PointerEvent) => e.stopPropagation();
onSelectEnd = (e: PointerEvent) => {
- document.removeEventListener("pointerup", this.onSelectEnd);
- document.removeEventListener("pointermove", this.onSelectMove);
- }
+ document.removeEventListener('pointerup', this.onSelectEnd);
+ document.removeEventListener('pointermove', this.onSelectMove);
+ };
onPointerUp = (e: React.PointerEvent): void => {
if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu();
if (!this._downEvent) return;
this._downEvent = false;
- if ((e.nativeEvent as any).formattedHandled) {
- console.log("handled");
- }
- if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) {
+ if (this.props.isContentActive(true)) {
const editor = this._editorView!;
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
!this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs);
+ FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc);
}
- (e.nativeEvent as any).formattedHandled = true;
- if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
+ if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
- }
+ };
@action
onDoubleClick = (e: React.MouseEvent): void => {
FormattedTextBoxComment.textBox = this;
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 click events
+ 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 click events
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
@@ -1277,31 +1491,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
FormattedTextBoxComment.Hide();
- (e.nativeEvent as any).formattedHandled = true;
-
if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
- }
+ };
setFocus = () => {
const pos = this._editorView?.state.selection.$from.pos || 1;
(this.ProseRef?.children?.[0] as any).focus();
setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
- }
+ };
@action
onFocused = (e: React.FocusEvent): void => {
//applyDevTools.applyDevTools(this._editorView);
FormattedTextBox.Focused = this;
- this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
- }
+ this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+ };
@observable public static Focused: FormattedTextBox | undefined;
onClick = (e: React.MouseEvent): void => {
+ if ((e.nativeEvent as any).handledByInnerReactInstance) {
+ e.stopPropagation();
+ return;
+ }
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.
+ 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) {
@@ -1311,29 +1528,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (!node && this.ProseRef) {
const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
const boundsRect = lastNode?.getBoundingClientRect();
- if (e.clientX > boundsRect.left && e.clientX < boundsRect.right &&
- e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+ if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && e.clientY > boundsRect.bottom) {
+ // if we clicked below the last prosemirror div, then set the selection to be the end of the document
this._editorView?.focus();
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) {
+ } else if (node && [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;
- }
- if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
- (e.nativeEvent as any).formattedHandled = true;
+ if (this.props.isSelected(true)) {
+ // if text box is selected, then it consumes all click events
+ (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)
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, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
this._forceUncollapse = false;
@@ -1356,39 +1569,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);
if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
if (!highlightOnly) {
- if (selectOrderedList || (!collapse && listNode.attrs.visibility)) {
+ if (selectOrderedList) {
this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
- } else if (!listNode.attrs.visibility || downNode === listNode) {
+ } else {
const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.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' });
}
}
}
- onMouseUp = (e: React.MouseEvent): void => {
- 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
- // are nested prosemirrors. We only want the lowest level prosemirror to be invoked.
- if (view.mouseDown) {
- const originalUpHandler = view.mouseDown.up;
- view.root.removeEventListener("mouseup", originalUpHandler);
- view.mouseDown.up = (e: MouseEvent) => {
- if (!(e as any).formattedHandled) {
- originalUpHandler(e);
- (e as any).formattedHandled = true;
- } else {
- console.log("prehandled");
- }
- };
- view.root.addEventListener("mouseup", view.mouseDown.up);
- }
- }
startUndoTypingBatch() {
- !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping"));
+ !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping'));
}
public endUndoTypingBatch() {
const wasUndoing = this._undoTyping;
@@ -1399,40 +1592,53 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@action
onBlur = (e: any) => {
+ if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
+ this.autoLink();
FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
- FormattedTextBox._hadSelection = window.getSelection()?.toString() !== "";
+ FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
this.endUndoTypingBatch();
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
const state = this._editorView!.state;
- const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
- if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
+ const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+ if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
try {
- translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => {
- setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => {
- this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0];
- }), 1000);
+ translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => {
+ setTimeout(
+ () =>
+ translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => {
+ this.dataDoc[this.fieldKey + '-translation'] = result1 + '\r\n\r\n' + result[0];
+ }),
+ 1000
+ );
});
- } catch (e: any) { console.log(e.message); }
+ } catch (e: any) {
+ console.log(e.message);
+ }
this._lastText = curText;
}
- }
- onKeyDown = (e: React.KeyboardEvent) => {
- // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container)
- if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) {
- return;
+ if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc['title-custom']) {
+ UndoManager.RunInBatch(() => {
+ this.dataDoc['title-custom'] = true;
+ this.dataDoc.showTitle = 'title';
+ const tr = this._editorView!.state.tr;
+ this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection());
+ }, 'titler');
}
+ };
+
+ onKeyDown = (e: React.KeyboardEvent) => {
if (e.altKey) {
e.preventDefault();
return;
}
const state = this._editorView!.state;
- if (!state.selection.empty && e.key === "%") {
+ if (!state.selection.empty && e.key === '%') {
this._rules!.EnteringStyle = true;
e.preventDefault();
e.stopPropagation();
@@ -1443,34 +1649,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._rules!.EnteringStyle = false;
}
e.stopPropagation();
- for (var i = state.selection.from; i < state.selection.to; i++) {
+ for (var i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
- if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
- mark.attrs.userid !== Doc.CurrentUserEmail) &&
- [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
e.preventDefault();
}
}
switch (e.key) {
- case "Escape":
+ case 'Escape':
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
return;
- case "Enter": this.insertTime();
- case "Tab": e.preventDefault(); break;
- default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
- case " ":
- this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({}))
- .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+ case 'Enter':
+ this.insertTime();
+ case 'Tab':
+ e.preventDefault();
+ break;
+ default:
+ if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
+ case ' ':
+ [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) &&
+ this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
this.startUndoTypingBatch();
- }
+ };
ondrop = (e: React.DragEvent) => {
this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
e.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.
- }
+ };
onScroll = (e: React.UIEvent) => {
if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
if (!this.props.dontSelectOnLoad) {
@@ -1479,142 +1687,164 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._ignoreScroll = false;
}
}
- }
+ };
tryUpdateScrollHeight = () => {
const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children) {
- const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins);
const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
- if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
+ if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {
+ // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight);
+ if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
} else {
setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
}
}
}
- }
- fitToBox = () => this.props.Document._fitToBox;
- sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
- sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ };
+ fitContentsToBox = () => this.props.Document._fitContentsToBox;
+ sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
// console.log("printting allSideBarDocs");
// console.log(this.allSidebarDocs);
return this.addDocument(doc, sidebarKey);
- }
+ };
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height;
- sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
- sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1));
+ setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '-height'] = height);
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ sidebarScreenToLocal = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0)
+ .scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1));
@computed get audioHandle() {
- return <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
- <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" />
- </div>;
+ return (
+ <div className="formattedTextBox-dictation" onClick={action(e => (this._recording = !this._recording))}>
+ <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
+ </div>
+ );
}
@computed get sidebarHandle() {
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
const color = !annotated ? Colors.WHITE : Colors.BLACK;
- const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
+ const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
- return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) :
- <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+ return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging()) ? null : (
+ <div
+ className="formattedTextBox-sidebar-handle"
+ onPointerDown={this.sidebarDown}
style={{
- left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
backgroundColor: backgroundColor,
color: color,
- opacity: annotated ? 1 : undefined
- }} >
- <FontAwesomeIcon icon={"comment-alt"} />
- </div>;
+ opacity: annotated ? 1 : undefined,
+ }}>
+ <FontAwesomeIcon icon={'comment-alt'} />
+ </div>
+ );
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
- const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView;
- return ComponentTag === CollectionStackingView ?
- <SidebarAnnos ref={this._sidebarRef}
+ const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'tree' ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+ return ComponentTag === CollectionStackingView ? (
+ <SidebarAnnos
+ ref={this._sidebarRef}
{...this.props}
fieldKey={this.fieldKey}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- // usePanelWidth={true}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
showSidebar={this.SidebarShown}
PanelWidth={this.sidebarWidth}
setHeight={this.setSidebarHeight}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
- /> :
- <ComponentTag
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.sidebarWidth}
- xPadding={0}
- yPadding={0}
- scaleField={this.SidebarKey + "-scale"}
- isAnnotationOverlay={false}
- select={emptyFunction}
- scaling={this.sidebarContentScaling}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.sidebarRemDocument}
- moveDocument={this.sidebarMoveDocument}
- addDocument={this.sidebarAddDocument}
- CollectionView={undefined}
- ScreenToLocalTransform={this.sidebarScreenToLocal}
- renderDepth={this.props.renderDepth + 1}
- setHeight={this.setSidebarHeight}
- fitContentsToDoc={this.fitToBox}
- noSidebar={true}
- fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />;
+ />
+ ) : (
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+ //@ts-ignore
+ <ComponentTag
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.sidebarWidth}
+ xPadding={0}
+ yPadding={0}
+ scaleField={this.SidebarKey + '-scale'}
+ isAnnotationOverlay={false}
+ select={emptyFunction}
+ NativeDimScaling={this.sidebarContentScaling}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ removeDocument={this.sidebarRemDocument}
+ moveDocument={this.sidebarMoveDocument}
+ addDocument={this.sidebarAddDocument}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
+ renderDepth={this.props.renderDepth + 1}
+ setHeight={this.setSidebarHeight}
+ fitContentsToBox={this.fitContentsToBox}
+ noSidebar={true}
+ treeViewHideTitle={true}
+ fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-sidebar`}
+ />
+ </div>
+ );
};
- return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.SelectedTool !== InkTool.None ? "-inking" : "")}
- style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
- </div>;
+ return (
+ <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
+ </div>
+ );
}
render() {
TraceMobx();
- const selected = this.props.isSelected();
- const active = this.props.isContentActive();
- const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
- const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
- const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
+ const active = this.props.isContentActive() || this.props.isSelected();
+ const selected = active;
+ const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+ const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : '';
+ const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
const minimal = this.props.ignoreAutoHeight;
const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
- const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0);
- const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : "";
- const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
- return (styleFromString?.height === "0px" ? (null) :
- <div className="formattedTextBox-cont"
+ const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
+ const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : '';
+ const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ return styleFromString?.height === '0px' ? null : (
+ <div
+ className="formattedTextBox-cont"
onWheel={e => this.props.isContentActive() && e.stopPropagation()}
style={{
transform: this.props.dontScale ? undefined : `scale(${scale})`,
- transformOrigin: this.props.dontScale ? undefined : "top left",
+ transformOrigin: this.props.dontScale ? undefined : 'top left',
width: this.props.dontScale ? undefined : `${100 / scale}%`,
height: this.props.dontScale ? undefined : `${100 / scale}%`,
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
- ...styleFromString
+ ...styleFromString,
}}>
- <div className={`formattedTextBox-cont`} ref={this._ref}
+ <div
+ className={`formattedTextBox-cont`}
+ ref={this._ref}
style={{
- overflow: this.autoHeight ? "hidden" : undefined,
- height: this.props.height || (this.autoHeight && this.props.renderDepth ? "max-content" : undefined),
+ overflow: this.autoHeight ? 'hidden' : undefined,
+ height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
- fontWeight: Cast(this.layoutDoc._fontWeight, "string", null) as any,
- fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
- pointerEvents: interactive ? undefined : "none",
+ fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any,
+ fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'),
+ pointerEvents: interactive ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -1624,32 +1854,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onBlur={this.onBlur}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
- onMouseUp={this.onMouseUp}
- onDoubleClick={this.onDoubleClick}
- >
- <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
+ onDoubleClick={this.onDoubleClick}>
+ <div
+ className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
+ ref={this._scrollRef}
style={{
- width: this.props.dontSelectOnLoad ? "100%" : `calc(100% - ${this.sidebarWidthPercent})`,
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
- overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
+ width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
+ pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+ overflow: this.layoutDoc._singleLine ? 'hidden' : undefined,
}}
- onScroll={this.onScroll} onDrop={this.ondrop} >
- <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
+ onScroll={this.onScroll}
+ onDrop={this.ondrop}>
+ <div
+ className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+ ref={this.createDropTarget}
style={{
padding: StrCast(this.layoutDoc._textBoxPadding),
paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? "none" : undefined) : undefined
+ pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
}}
/>
</div>
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle}
- {!this.layoutDoc._showAudio ? (null) : this.audioHandle}
+ {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
+ {!this.layoutDoc._showAudio ? null : this.audioHandle}
</div>
- </div >
+ </div>
);
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 1fde6e5f0..bdf59863b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,17 +1,25 @@
-import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc } from "../../../../fields/Doc";
-import { LinkDocPreview } from "../LinkDocPreview";
-import { FormattedTextBox } from "./FormattedTextBox";
+import { Mark, ResolvedPos } from 'prosemirror-model';
+import { EditorState } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
+import { LinkDocPreview } from '../LinkDocPreview';
+import { FormattedTextBox } from './FormattedTextBox';
import './FormattedTextBoxComment.scss';
-import { schema } from "./schema_rts";
+import { schema } from './schema_rts';
-export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); }
-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.linkAnchor); }
-export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let before = 0, nbef = rpos.nodeBefore;
+export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid);
+}
+export function findLinkMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor);
+}
+export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
+ let before = 0,
+ nbef = rpos.nodeBefore;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
@@ -19,8 +27,9 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma
}
return before;
}
-export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let after = 0, naft = rpos.nodeAfter;
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
+ let after = 0,
+ naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
@@ -31,7 +40,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
// this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
// this will also display metadata information about text when the view is configured to display things like other people who authored text.
-//
+//
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
@@ -42,11 +51,11 @@ export class FormattedTextBoxComment {
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
- const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div");
- const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div");
- tooltip.className = "FormattedTextBox-tooltip";
- tooltipText.className = "FormattedTextBox-tooltipText";
- tooltip.style.display = "none";
+ const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div'));
+ const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div'));
+ tooltip.className = 'FormattedTextBox-tooltip';
+ tooltipText.className = 'FormattedTextBox-tooltipText';
+ tooltip.style.display = 'none';
tooltip.appendChild(tooltipText);
tooltip.onpointerdown = (e: PointerEvent) => {
const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
@@ -54,42 +63,51 @@ export class FormattedTextBoxComment {
e.stopPropagation();
e.preventDefault();
};
- document.getElementById("root")?.appendChild(tooltip);
+ document.getElementById('root')?.appendChild(tooltip);
}
}
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
- FormattedTextBoxComment.tooltip.style.display = "none";
+ FormattedTextBoxComment.tooltip.style.display = 'none';
}
public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
FormattedTextBoxComment.startUserMarkRegion = start;
FormattedTextBoxComment.endUserMarkRegion = end;
FormattedTextBoxComment.userMark = mark;
- FormattedTextBoxComment.tooltip.style.display = "";
+ FormattedTextBoxComment.tooltip.style.display = '';
}
static showCommentbox(view: EditorView, nbef: number) {
const state = view.state;
// These are in screen coordinates
- const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+ const start = view.coordsAtPos(state.selection.from - nbef),
+ end = view.coordsAtPos(state.selection.from - nbef);
// The box in which the tooltip is positioned, to use as base
- const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+ const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
// Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
const left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
- FormattedTextBoxComment.tooltip.style.display = "";
+ FormattedTextBoxComment.tooltip.style.left = left - box.left + 'px';
+ FormattedTextBoxComment.tooltip.style.bottom = box.bottom - start.top + 'px';
+ FormattedTextBoxComment.tooltip.style.display = '';
}
- static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "") {
+ static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '') {
FormattedTextBoxComment.textBox = textBox;
- if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) {
- FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h));
+ if (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection)) {
+ FormattedTextBoxComment.setupPreview(
+ view,
+ textBox,
+ hrefs
+ ?.trim()
+ .split(' ')
+ .filter(h => h),
+ linkDoc
+ );
}
}
- static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) {
+ static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string) {
const state = view.state;
// this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
if (state.selection.$from) {
@@ -103,24 +121,27 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
}
if (mark && child && ((nbef && naft) || !noselection)) {
- FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString();
+ FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + ' on ' + new Date(mark.attrs.modified * 1000).toLocaleString();
FormattedTextBoxComment.showCommentbox(view, nbef);
} else FormattedTextBoxComment.Hide();
}
- // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
+ // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
if (state.selection.$from && hrefs?.length) {
const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- nbef && naft && LinkDocPreview.SetLinkInfo({
- docProps: textBox.props,
- linkSrc: textBox.rootDoc,
- location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
- hrefs,
- showHeader: true
- });
+ nbef &&
+ naft &&
+ LinkDocPreview.SetLinkInfo({
+ docProps: textBox.props,
+ linkSrc: textBox.rootDoc,
+ linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
+ location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
+ hrefs,
+ showHeader: true,
+ });
}
}
- destroy() { }
-} \ No newline at end of file
+ destroy() {}
+}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index c76eda859..31552cf1b 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,22 +1,17 @@
-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 { Schema } from "prosemirror-model";
-import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
-import { splitListItem, wrapInList, } from "prosemirror-schema-list";
-import { EditorState, Transaction, TextSelection } from "prosemirror-state";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym, DocListCast, AclAugment, AclSelfEdit } from "../../../../fields/Doc";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { Id } from "../../../../fields/FieldSymbols";
-import { Docs } from "../../../documents/Documents";
-import { Utils } from "../../../../Utils";
-import { listSpec } from "../../../../fields/Schema";
-import { List } from "../../../../fields/List";
-import { GetEffectiveAcl } from "../../../../fields/util";
-
-const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
+import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
+import { redo, undo } from 'prosemirror-history';
+import { Schema } from 'prosemirror-model';
+import { splitListItem, wrapInList } from 'prosemirror-schema-list';
+import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
+import { liftTarget } from 'prosemirror-transform';
+import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc';
+import { GetEffectiveAcl } from '../../../../fields/util';
+import { Utils } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
+
+const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
export type KeyMap = { [key: string]: any };
@@ -25,12 +20,12 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
tx2.doc.descendants((node: any, offset: any, index: any) => {
if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
const path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) {
if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
depth++;
}
- tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth, }, node.marks);
+ tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth }, node.marks);
}
});
return tx2;
@@ -48,32 +43,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
keys[key] = cmd;
}
- /// bcz; Argh!! replace with an onEnter func that conditionally handles Enter
- const addTextBox = (below: boolean, force?: boolean) => {
- if (props.Document.treeViewType === "outline") return true; // bcz: Arghh .. need to determine if this is an treeViewOutlineBox in which case Enter's are ignored..
- const layoutDoc = props.Document;
- const originalDoc = layoutDoc.rootDocument || layoutDoc;
- if (force || props.Document._singleLine) {
- const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Doc.MakeCopy(originalDoc, true);
- const dataField = originalDoc[Doc.LayoutFieldKey(newDoc)];
- newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (below) newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
- else newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
- if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
- newDoc[layoutKey] = originalDoc[layoutKey];
- }
- Doc.GetProto(newDoc).text = undefined;
- FormattedTextBox.SelectOnLoad = newDoc[Id];
- props.addDocument(newDoc);
- return true;
- }
- return false;
- };
-
const canEdit = (state: any) => {
- switch (GetEffectiveAcl(props.Document)) {
- case AclAugment: return false;
+ switch (GetEffectiveAcl(props.DataDoc)) {
+ case AclAugment:
+ return false;
case AclSelfEdit:
for (var i = state.selection.from; i < state.selection.to; i++) {
const marks = state.doc.resolve(i)?.marks?.();
@@ -86,96 +59,102 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return true;
};
- const toggleEditableMark = (mark: any) => (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+ const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
//History commands
- bind("Mod-z", undo);
- bind("Shift-Mod-z", redo);
- !mac && bind("Mod-y", redo);
+ bind('Mod-z', undo);
+ bind('Shift-Mod-z', redo);
+ !mac && bind('Mod-y', redo);
//Commands to modify Mark
- bind("Mod-b", toggleEditableMark(schema.marks.strong));
- bind("Mod-B", toggleEditableMark(schema.marks.strong));
+ bind('Mod-b', toggleEditableMark(schema.marks.strong));
+ bind('Mod-B', toggleEditableMark(schema.marks.strong));
- bind("Mod-e", toggleEditableMark(schema.marks.em));
- bind("Mod-E", toggleEditableMark(schema.marks.em));
+ bind('Mod-e', toggleEditableMark(schema.marks.em));
+ bind('Mod-E', toggleEditableMark(schema.marks.em));
- bind("Mod-*", toggleEditableMark(schema.marks.code));
+ bind('Mod-*', toggleEditableMark(schema.marks.code));
- bind("Mod-u", toggleEditableMark(schema.marks.underline));
- bind("Mod-U", toggleEditableMark(schema.marks.underline));
+ bind('Mod-u', toggleEditableMark(schema.marks.underline));
+ bind('Mod-U', toggleEditableMark(schema.marks.underline));
//Commands for lists
- bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
-
- bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- /// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab);
- if (props.Document._singleLine) {
- if (!props.LayoutTemplateString) return addTextBox(false, true);
- return true;
- }
+ bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
+
+ bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true));
+ bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true));
+ bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true));
+ bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) { // couldn't sink into an existing list, so wrap in a new one
- const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
- if (!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+ if (
+ !sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
- // when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
dispatch(tx3);
- })) {
- console.log("bullet promote fail");
+ })
+ ) {
+ // couldn't sink into an existing list, so wrap in a new one
+ const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
+ if (
+ !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ // when promoting to a list, assume list will format things so don't copy the stored marks.
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
+ console.log('bullet promote fail');
}
}
});
- bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- /// bcz; Argh!! replace with a onShiftTab prop conditionally handles Tab);
- if (props.Document._singleLine) return true;
+ bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) {
- console.log("bullet demote fail");
+ if (
+ !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
+ console.log('bullet demote fail');
}
});
//Command to create a new Tab with a PDF of all the command shortcuts
- bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 });
- props.addDocTab(newDoc, "add:right");
+ bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
+ props.addDocTab(newDoc, 'add:right');
});
//Commands to modify BlockType
- bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
- bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
- bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
+ bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
+ bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
+ bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
- bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))));
+ bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() }))));
for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
+ bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
}
//Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
+ bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
//Command to unselect all
- bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
@@ -187,25 +166,29 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return tx;
};
- //Command to create a text document to the right of the selected textbox
- bind("Alt-Enter", () => addTextBox(false, true));
-
- //Command to create a text document to the bottom of the selected textbox
- bind("Ctrl-Enter", () => addTextBox(true, true));
+ bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
- bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
- if (!deleteSelection(state, (tx: Transaction<S>) => {
- dispatch(updateBullets(tx, schema));
- })) {
- if (!joinBackward(state, (tx: Transaction<S>) => {
+ if (
+ !deleteSelection(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
- })) {
- if (!selectNodeBackward(state, (tx: Transaction<S>) => {
+ })
+ ) {
+ if (
+ !joinBackward(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
- })) {
+ })
+ ) {
+ if (
+ !selectNodeBackward(state, (tx: Transaction) => {
+ dispatch(updateBullets(tx, schema));
+ })
+ ) {
return false;
}
}
@@ -215,9 +198,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
//command to break line
- bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
- if (addTextBox(true, false)) return true;
-
+ bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const trange = state.selection.$from.blockRange(state.selection.$to);
@@ -230,25 +212,29 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
}
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- const cr = state.selection.$from.node().textContent.endsWith("\n");
+ const cr = state.selection.$from.node().textContent.endsWith('\n');
if (cr || !newlineInCode(state, dispatch as any)) {
- if (!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) {
+ if (
+ !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
const fromattrs = state.selection.$from.node().attrs;
- if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
- const tonode = tx3.selection.$to.node();
- if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
- 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);
- }
- } else dispatch(tx3.insertText("\r\n"));
- })) {
+ if (
+ !splitBlockKeepMarks(state, (tx3: Transaction) => {
+ const tonode = tx3.selection.$to.node();
+ if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
+ 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) => void)) {
+ dispatch(tx4);
+ }
+ } else dispatch(tx3.insertText('\r\n'));
+ })
+ ) {
return false;
}
}
@@ -257,16 +243,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
//Command to create a blank space
- bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
- bind("Alt-ArrowUp", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinUp(state, dispatch as any));
- bind("Alt-ArrowDown", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinDown(state, dispatch as any));
- bind("Mod-BracketLeft", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && lift(state, dispatch as any));
+ bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any));
+ bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any));
+ bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any));
const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
@@ -276,10 +262,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return false;
});
- // mac && bind("Ctrl-Enter", cmd);
- // bind("Mod-Enter", cmd);
- bind("Shift-Enter", cmd);
+ bind('Shift-Enter', cmd);
return keys;
}
-
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 4814d6e9a..2a77210ae 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,40 +1,40 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx";
-import { observer } from "mobx-react";
-import { lift, wrapIn } from "prosemirror-commands";
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
-import { wrapInList } from "prosemirror-schema-list";
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc } from "../../../../fields/Doc";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { DocServer } from "../../../DocServer";
-import { LinkManager } from "../../../util/LinkManager";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
-import { FieldViewProps } from "../FieldView";
-import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox";
-import { updateBullets } from "./ProsemirrorExampleTransfer";
-import "./RichTextMenu.scss";
-import { schema } from "./schema_rts";
-const { toggleMark } = require("prosemirror-commands");
-
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { lift, wrapIn } from 'prosemirror-commands';
+import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
+import { wrapInList } from 'prosemirror-schema-list';
+import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
+import { Doc } from '../../../../fields/Doc';
+import { Cast, StrCast } from '../../../../fields/Types';
+import { DocServer } from '../../../DocServer';
+import { LinkManager } from '../../../util/LinkManager';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { FieldViewProps } from '../FieldView';
+import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
+import { updateBullets } from './ProsemirrorExampleTransfer';
+import './RichTextMenu.scss';
+import { schema } from './schema_rts';
+const { toggleMark } = require('prosemirror-commands');
@observer
-export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
+export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable static Instance: RichTextMenu;
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
private _linkToRef = React.createRef<HTMLInputElement>();
@observable public view?: EditorView;
- public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+ public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
@observable private collapsed: boolean = false;
+ @observable private _noLinkActive: boolean = false;
@observable private _boldActive: boolean = false;
@observable private _italicsActive: boolean = false;
@observable private _underlineActive: boolean = false;
@@ -42,53 +42,60 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _subscriptActive: boolean = false;
@observable private _superscriptActive: boolean = false;
- @observable private _activeFontSize: string = "13px";
- @observable private _activeFontFamily: string = "";
- @observable private activeListType: string = "";
- @observable private _activeAlignment: string = "left";
+ @observable private _activeFontSize: string = '13px';
+ @observable private _activeFontFamily: string = '';
+ @observable private activeListType: string = '';
+ @observable private _activeAlignment: string = 'left';
@observable private brushMarks: Set<Mark> = new Set();
@observable private showBrushDropdown: boolean = false;
- @observable private _activeFontColor: string = "black";
+ @observable private _activeFontColor: string = 'black';
@observable private showColorDropdown: boolean = false;
- @observable private activeHighlightColor: string = "transparent";
+ @observable private activeHighlightColor: string = 'transparent';
@observable private showHighlightDropdown: boolean = false;
- @observable private currentLink: string | undefined = "";
+ @observable private currentLink: string | undefined = '';
@observable private showLinkDropdown: boolean = false;
_reaction: IReactionDisposer | undefined;
- _delayHide = false;
constructor(props: Readonly<{}>) {
super(props);
runInAction(() => {
RichTextMenu.Instance = this;
this._canFade = false;
- //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
this.Pinned = true;
});
}
- componentDidMount() {
- this._reaction = reaction(() => SelectionManager.Views(),
- () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true));
+ @computed get noAutoLink() {
+ return this._noLinkActive;
}
- componentWillUnmount() {
- this._reaction?.();
+ @computed get bold() {
+ return this._boldActive;
+ }
+ @computed get underline() {
+ return this._underlineActive;
+ }
+ @computed get italics() {
+ return this._italicsActive;
+ }
+ @computed get strikeThrough() {
+ return this._strikethroughActive;
+ }
+ @computed get fontColor() {
+ return this._activeFontColor;
+ }
+ @computed get fontFamily() {
+ return this._activeFontFamily;
+ }
+ @computed get fontSize() {
+ return this._activeFontSize;
+ }
+ @computed get textAlign() {
+ return this._activeAlignment;
}
-
- @computed get bold() { return this._boldActive; }
- @computed get underline() { return this._underlineActive; }
- @computed get italics() { return this._italicsActive; }
- @computed get strikeThrough() { return this._strikethroughActive; }
- @computed get fontColor() { return this._activeFontColor; }
- @computed get fontFamily() { return this._activeFontFamily; }
- @computed get fontSize() { return this._activeFontSize; }
- @computed get textAlign() { return this._activeAlignment; }
-
- public delayHide = () => this._delayHide = true;
@action
public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) {
@@ -117,16 +124,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
- this._activeFontSize = !activeSizes.length ? "13px" : activeSizes[0];
- this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "...";
- this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "...";
+ this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
}
- setMark = (mark: Mark, state: EditorState<any>, dispatch: any, dontToggle: boolean = false) => {
+ setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
const node = (state.selection as NodeSelection).node;
if (node?.type === schema.nodes.ordered_list) {
@@ -139,7 +146,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
} else if (dontToggle) {
toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
const { from, $from, to, empty } = tx.selection;
- if (!tx.doc.rangeHasMark(from, to, mark.type)) { // hack -- should have just set the mark in the first place
+ if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+ // hack -- should have just set the mark in the first place
toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
} else dispatch(tx);
});
@@ -147,7 +155,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
- }
+ };
// finds font sizes and families in selection
getActiveAlignment() {
@@ -155,11 +163,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const path = (this.view.state.selection.$from as any).path;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
- return path[i].attrs.align || "left";
+ return path[i].attrs.align || 'left';
}
}
}
- return "left";
+ return 'left';
}
// finds font sizes and families in selection
@@ -175,7 +183,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle;
}
}
- return "";
+ return '';
}
// finds font sizes and families in selection
@@ -189,7 +197,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.TextView.props.isSelected(true)) {
const state = this.view.state;
const pos = this.view.state.selection.$from;
- const marks: Mark<any>[] = [...(state.storedMarks ?? [])];
+ const marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.selection.empty) {
const ref_node = this.reference_node(pos);
marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
@@ -208,10 +216,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return { activeFamilies, activeSizes, activeColors, activeHighlights };
}
- getMarksInSelection(state: EditorState<any>) {
+ getMarksInSelection(state: EditorState) {
const found = new Set<Mark>();
const { from, to } = state.selection as TextSelection;
- state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+ state.doc.nodesBetween(from, to, node => node.marks.forEach(m => found.add(m)));
return found;
}
@@ -220,7 +228,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
let activeMarks: MarkType[] = [];
if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks;
- const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+ const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
//current selection
const { empty, ranges, $to } = this.view.state.selection as TextSelection;
@@ -233,14 +241,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
return false;
});
- }
- else {
+ } else {
const pos = this.view.state.selection.$from;
const ref_node: ProsNode | null = this.reference_node(pos);
if (ref_node !== null && ref_node !== this.view.state.doc) {
if (ref_node.isText) {
- }
- else {
+ } else {
return [];
}
activeMarks = markGroup.filter(mark_type => {
@@ -264,6 +270,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
if (!activeMarks) return;
+ this._noLinkActive = false;
this._boldActive = false;
this._italicsActive = false;
this._underlineActive = false;
@@ -273,23 +280,46 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
activeMarks.forEach(mark => {
switch (mark.name) {
- case "strong": this._boldActive = true; break;
- case "em": this._italicsActive = true; break;
- case "underline": this._underlineActive = true; break;
- case "strikethrough": this._strikethroughActive = true; break;
- case "subscript": this._subscriptActive = true; break;
- case "superscript": this._superscriptActive = true; break;
+ case 'noAutoLinkAnchor':
+ this._noLinkActive = true;
+ break;
+ case 'strong':
+ this._boldActive = true;
+ break;
+ case 'em':
+ this._italicsActive = true;
+ break;
+ case 'underline':
+ this._underlineActive = true;
+ break;
+ case 'strikethrough':
+ this._strikethroughActive = true;
+ break;
+ case 'subscript':
+ this._subscriptActive = true;
+ break;
+ case 'superscript':
+ this._superscriptActive = true;
+ break;
}
});
}
+ toggleNoAutoLinkAnchor = () => {
+ if (this.view) {
+ const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor);
+ this.setMark(mark, this.view.state, this.view.dispatch, false);
+ this.TextView.autoLink();
+ this.view.focus();
+ }
+ };
toggleBold = () => {
if (this.view) {
const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong);
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
+ };
toggleUnderline = () => {
if (this.view) {
@@ -297,7 +327,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
+ };
toggleItalics = () => {
if (this.view) {
@@ -305,17 +335,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
-
+ };
setFontSize = (fontSize: string) => {
if (this.view) {
- const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
- this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
- this.view.focus();
- this.updateMenu(this.view, undefined, this.props);
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
+ this.TextView.dataDoc.fontSize = fontSize;
+ this.view.focus();
+ this.updateMenu(this.view, undefined, this.props);
+ } else {
+ const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
+ this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+ this.view.focus();
+ this.updateMenu(this.view, undefined, this.props);
+ }
}
- }
+ };
setFontFamily = (family: string) => {
if (this.view) {
@@ -324,7 +359,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
}
- }
+ };
setHighlight(color: String, view: EditorView, dispatch: any) {
const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color });
@@ -344,8 +379,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
- changeListType = (nodeType: Node | undefined) => {
- if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return;
+ changeListType = (mapStyle: string) => {
+ const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
+ const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle });
+ if (!this.view || nodeType?.attrs.mapStyle === '') return;
const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
let inList: any = undefined;
@@ -359,17 +396,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
- if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
+ if (
+ inList ||
+ !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
- this.view!.dispatch(tx2);
- })) {
+ this.view!.dispatch(tx2);
+ })
+ ) {
const tx2 = this.view.state.tr;
if (nodeType && (inList || nextIsOL)) {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from,
- inList ? fromList + inList.nodeSize : this.view.state.selection.to);
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
this.view.dispatch(tx3);
@@ -377,9 +416,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
- }
+ };
- insertSummarizer(state: EditorState<any>, dispatch: any) {
+ insertSummarizer(state: EditorState, dispatch: any) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
const tr = state.tr;
@@ -390,7 +429,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- align = (view: EditorView, dispatch: any, alignment: "left" | "right" | "center") => {
+ align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView.props.isSelected(true)) {
var tr = view.state.tr;
view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
@@ -404,9 +443,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
view.focus();
dispatch?.(tr);
}
- }
+ };
- insetParagraph(state: EditorState<any>, dispatch: any) {
+ insetParagraph(state: EditorState, 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 || node.type === schema.nodes.heading) {
@@ -419,7 +458,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
dispatch?.(tr);
return true;
}
- outsetParagraph(state: EditorState<any>, dispatch: any) {
+ outsetParagraph(state: EditorState, 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 || node.type === schema.nodes.heading) {
@@ -433,7 +472,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- indentParagraph(state: EditorState<any>, dispatch: any) {
+ indentParagraph(state: EditorState, dispatch: any) {
var tr = state.tr;
const heading = false;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
@@ -449,7 +488,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- hangingIndentParagraph(state: EditorState<any>, dispatch: any) {
+ hangingIndentParagraph(state: EditorState, 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 || node.type === schema.nodes.heading) {
@@ -464,7 +503,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertBlockquote(state: EditorState<any>, dispatch: any) {
+ insertBlockquote(state: EditorState, dispatch: any) {
const path = (state.selection.$from as any).path;
if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
lift(state, dispatch);
@@ -474,20 +513,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertHorizontalRule(state: EditorState<any>, dispatch: any) {
+ insertHorizontalRule(state: EditorState, dispatch: any) {
dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView());
return true;
}
- @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+ @action toggleBrushDropdown() {
+ this.showBrushDropdown = !this.showBrushDropdown;
+ }
// todo: add brushes to brushMap to save with a style name
onBrushNameKeyPress = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
- this._brushNameRef.current!.style.background = "lightGray";
+ this._brushNameRef.current!.style.background = 'lightGray';
}
- }
+ };
_brushNameRef = React.createRef<HTMLInputElement>();
@action
@@ -496,7 +537,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- fillBrush(state: EditorState<any>, dispatch: any) {
+ fillBrush(state: EditorState, dispatch: any) {
if (!this.view) return;
if (!Array.from(this.brushMarks.keys()).length) {
@@ -504,68 +545,81 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (selected_marks.size >= 0) {
this.brushMarks = selected_marks;
}
- }
- else {
+ } else {
const { from, to, $from } = this.view.state.selection;
if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
if (to - from > 0) {
this.view.dispatch(this.view.state.tr.removeMark(from, to));
- Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
- this.setMark(mark, this.view!.state, this.view!.dispatch);
- });
+ Array.from(this.brushMarks)
+ .filter(m => m.type !== schema.marks.user_mark)
+ .forEach((mark: Mark) => {
+ this.setMark(mark, this.view!.state, this.view!.dispatch);
+ });
}
}
}
}
- get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
- get TextViewFieldKey() { return this.TextView?.props.fieldKey; }
-
-
-
- @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
+ get TextView() {
+ return (this.view as any)?.TextView as FormattedTextBox;
+ }
+ get TextViewFieldKey() {
+ return this.TextView?.props.fieldKey;
+ }
+ @action setActiveHighlight(color: string) {
+ this.activeHighlightColor = color;
+ }
- @action setCurrentLink(link: string) { this.currentLink = link; }
+ @action setCurrentLink(link: string) {
+ this.currentLink = link;
+ }
createLinkButton() {
const self = this;
function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
self.TextView?.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change");
+ UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change');
}
- const link = this.currentLink ? this.currentLink : "";
+ const link = this.currentLink ? this.currentLink : '';
- const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button">
- <FontAwesomeIcon icon="link" size="lg" />
- </button>
- </Tooltip>;
+ const button = (
+ <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
+ <button className="antimodeMenu-button color-preview-button">
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
+ </Tooltip>
+ );
- const dropdownContent =
+ const dropdownContent = (
<div className="dropdown link-menu">
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
- <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
+ <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+ Apply hyperlink
+ </button>
<div className="divider" />
- <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
- </div>;
+ <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+ Remove link
+ </button>
+ </div>
+ );
- return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+ return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
}
async getTextLinkTargetTitle() {
if (!this.view) return;
const node = this.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type.name === "link");
+ const link = node && node.marks.find(m => m.type.name === 'link');
if (link) {
const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
if (href) {
if (href.indexOf(Doc.localServerPath()) === 0) {
- const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0];
+ const linkclicked = href.replace(Doc.localServerPath(), '').split('?')[0];
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
@@ -594,8 +648,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
- }
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
+ };
@undoBatch
@action
@@ -606,13 +660,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const allAnchors = linkAnchor.attrs.allAnchors.slice();
this.TextView.RemoveAnchorFromSelection(allAnchors);
// 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.
- allAnchors.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0).forEach((aref: any) => {
- const anchorId = aref.href.replace(Doc.localServerPath(), "").split("?")[0];
- anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
- });
+ allAnchors
+ .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0)
+ .forEach((aref: any) => {
+ const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
+ anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ });
}
}
- }
+ };
linkExtend($start: ResolvedPos, href: string) {
const mark = this.view!.state.schema.marks.linkAnchor;
@@ -633,7 +689,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return { from: startPos, to: endPos };
}
- reference_node(pos: ResolvedPos<any>): ProsNode | null {
+ reference_node(pos: ResolvedPos): ProsNode | null {
if (!this.view) return null;
let ref_node: ProsNode = this.view.state.doc;
@@ -653,7 +709,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
ref_node = node;
skip = true;
}
-
});
}
}
@@ -737,21 +792,19 @@ interface ButtonDropdownProps {
openDropdownOnButton?: boolean;
link?: boolean;
pdf?: boolean;
-
}
@observer
export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
-
@observable private showDropdown: boolean = false;
private ref: HTMLDivElement | null = null;
componentDidMount() {
- document.addEventListener("pointerdown", this.onBlur);
+ document.addEventListener('pointerdown', this.onBlur);
}
componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onBlur);
+ document.removeEventListener('pointerdown', this.onBlur);
}
@action
@@ -767,7 +820,7 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
e.preventDefault();
e.stopPropagation();
this.toggleDropdown();
- }
+ };
onBlur = (e: PointerEvent) => {
setTimeout(() => {
@@ -775,37 +828,40 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
this.setShowDropdown(false);
}
}, 0);
- }
-
+ };
render() {
return (
- <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
- {!this.props.pdf ?
+ <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
+ {!this.props.pdf ? (
<div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
{this.props.button}
- <div style={{ marginTop: "-8.5", position: "relative" }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+ <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
- :
+ ) : (
<>
{this.props.button}
<button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</button>
- </>}
- {this.showDropdown ? this.props.dropdownContent : (null)}
+ </>
+ )}
+ {this.showDropdown ? this.props.dropdownContent : null}
</div>
);
}
}
-
interface RichTextMenuPluginProps {
editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- render() { return null; }
- update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); }
-} \ No newline at end of file
+ render() {
+ return null;
+ }
+ update(view: EditorView, lastState: EditorState | undefined) {
+ RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps);
+ }
+}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index bafae84dc..1916b94bf 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,17 +1,17 @@
-import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules";
-import { NodeSelection, TextSelection } from "prosemirror-state";
-import { DataSym, Doc } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { NumCast, StrCast } from "../../../../fields/Types";
-import { normalizeEmail } from "../../../../fields/util";
-import { returnFalse, Utils } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { wrappingInputRule } from "./prosemirrorPatches";
-import { RichTextMenu } from "./RichTextMenu";
-import { schema } from "./schema_rts";
+import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
+import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { DataSym, Doc } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { ComputedField } from '../../../../fields/ScriptField';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { normalizeEmail } from '../../../../fields/util';
+import { returnFalse, Utils } from '../../../../Utils';
+import { DocServer } from '../../../DocServer';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { FormattedTextBox } from './FormattedTextBox';
+import { wrappingInputRule } from './prosemirrorPatches';
+import { RichTextMenu } from './RichTextMenu';
+import { schema } from './schema_rts';
export class RichTextRules {
public Document: Doc;
@@ -34,9 +34,9 @@ export class RichTextRules {
wrappingInputRule(
/^1\.\s$/,
schema.nodes.ordered_list,
- () => ({ mapStyle: "decimal", bulletStyle: 1 }),
+ () => ({ mapStyle: 'decimal', bulletStyle: 1 }),
(match: any, node: any) => node.childCount + node.attrs.order === +match[1],
- ((type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'decimal', bulletStyle: 1 } })) as any
),
// A. create alphabetical ordered list
@@ -45,341 +45,324 @@ export class RichTextRules {
schema.nodes.ordered_list,
// match => {
() => {
- return ({ mapStyle: "multi", bulletStyle: 1 });
+ return { mapStyle: 'multi', bulletStyle: 1 };
// return ({ order: +match[1] })
},
(match: any, node: any) => {
return node.childCount + node.attrs.order === +match[1];
},
- ((type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
),
// * + - create bullet list
- wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list,
+ wrappingInputRule(
+ /^\s*([-+*])\s$/,
+ schema.nodes.ordered_list,
// match => {
- () => ({ mapStyle: "bullet" }), // ({ order: +match[1] })
+ () => ({ mapStyle: 'bullet' }), // ({ order: +match[1] })
(match: any, node: any) => node.childCount + node.attrs.order === +match[1],
- ((type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'bullet' } })) as any
),
// ``` create code block
textblockTypeInputRule(/^```$/, schema.nodes.code_block),
- // %<font-size> set the font size
- new InputRule(
- new RegExp(/%([0-9]+)\s$/),
- (state, match, start, end) => {
- const size = Number(match[1]);
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
- }),
+ // %<font-size> set the font size
+ new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
+ const size = Number(match[1]);
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
+ }),
//Create annotation to a field on the text document
- new InputRule(
- new RegExp(/>>$/),
- (state, match, start, end) => {
- const textDoc = this.Document[DataSym];
- const numInlines = NumCast(textDoc.inlineTextCount);
- textDoc.inlineTextCount = numInlines + 1;
- const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
- const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
- const textDocInline = Docs.Create.TextDocument("", { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9px", title: "inline comment" });
- textDocInline.title = inlineFieldKey; // give the annotation its own title
- textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
- textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
- textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
- textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
- textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
- textDoc[inlineFieldKey] = ""; // set a default value for the annotation
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" });
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced;
- }),
-
+ new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
+ const textDoc = this.Document[DataSym];
+ const numInlines = NumCast(textDoc.inlineTextCount);
+ textDoc.inlineTextCount = numInlines + 1;
+ const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to
+ const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
+ const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' });
+ textDocInline.title = inlineFieldKey; // give the annotation its own title
+ textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+ textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+ textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
+ textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
+ textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+ textDoc[inlineFieldKey] = ''; // set a default value for the annotation
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: textDocInline[Id], float: 'right' });
+ const sm = state.storedMarks || undefined;
+ const replaced = node
+ ? state.tr
+ .insert(start, newNode)
+ .replaceRangeWith(start + 1, end + 1, dashDoc)
+ .insertText(' ', start + 2)
+ .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ : state.tr;
+ return replaced;
+ }),
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/(%d|d)$/),
- (state, match, start, end) => {
- if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
- const pos = (state.doc.resolve(start) as any);
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => {
+ if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ const pos = state.doc.resolve(start) as any;
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/(%h|h)$/),
- (state, match, start, end) => {
- if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
- const pos = (state.doc.resolve(start) as any);
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => {
+ if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ const pos = state.doc.resolve(start) as any;
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/(%q|q)$/),
- (state, match, start, end) => {
- if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
- const pos = (state.doc.resolve(start) as any);
- if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
- const node = state.selection.node;
- return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
- }
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => {
+ if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ const pos = state.doc.resolve(start) as any;
+ if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
+ const node = state.selection.node;
+ return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
+ }
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// center justify text
- new InputRule(
- new RegExp(/%\^/),
- (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
- if (resolved?.parent.type.name === "paragraph") {
- return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "center" }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }
- }),
+ new InputRule(new RegExp(/%\^/), (state, match, start, end) => {
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === 'paragraph') {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
+ }),
// left justify text
- new InputRule(
- new RegExp(/%\[/),
- (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
- if (resolved?.parent.type.name === "paragraph") {
- return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "left" }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }
- }),
+ new InputRule(new RegExp(/%\[/), (state, match, start, end) => {
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === 'paragraph') {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
+ }),
// right justify text
- new InputRule(
- new RegExp(/%\]/),
- (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
- if (resolved?.parent.type.name === "paragraph") {
- return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "right" }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }
- }),
-
+ new InputRule(new RegExp(/%\]/), (state, match, start, end) => {
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === 'paragraph') {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
+ }),
// %f create footnote
- new InputRule(
- new RegExp(/%f$/),
- (state, match, start, end) => {
- const newNode = schema.nodes.footnote.create({});
- const tr = state.tr;
- tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
- return tr.setSelection(new NodeSelection( // select the footnote node to open its display
- tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
- tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0))));
- }),
+ new InputRule(new RegExp(/%f$/), (state, match, start, end) => {
+ const newNode = schema.nodes.footnote.create({});
+ const tr = state.tr;
+ tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
+ return tr.setSelection(
+ new NodeSelection( // select the footnote node to open its display
+ tr.doc.resolve(
+ // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+ tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)
+ )
+ )
+ );
+ }),
// activate a style by name using prefix '%<color name>'
- new InputRule(
- new RegExp(/%[a-z]+$/),
- (state, match, start, end) => {
-
- const color = match[0].substring(1, match[0].length);
- const marks = RichTextMenu.Instance._brushMap.get(color);
+ new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => {
+ const color = match[0].substring(1, match[0].length);
+ const marks = RichTextMenu.Instance._brushMap.get(color);
- if (marks) {
- const tr = state.tr.deleteRange(start, end);
- return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
- }
+ if (marks) {
+ const tr = state.tr.deleteRange(start, end);
+ return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+ }
- const isValidColor = (strColor: string) => {
- const s = new Option().style;
- s.color = strColor;
- return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
- };
+ const isValidColor = (strColor: string) => {
+ const s = new Option().style;
+ s.color = strColor;
+ return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+ };
- if (isValidColor(color)) {
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
- }
+ if (isValidColor(color)) {
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+ }
- return null;
- }),
+ return null;
+ }),
// stop using active style
- new InputRule(
- new RegExp(/%%$/),
- (state, match, start, end) => {
-
- const tr = state.tr.deleteRange(start, end);
- const marks = state.tr.selection.$anchor.nodeBefore?.marks;
-
- return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
- }),
-
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
- // [[<fieldKey> : <Doc>]]
- // [[:Doc]] => hyperlink
- // [[fieldKey]] => show field
+ new InputRule(new RegExp(/%%$/), (state, match, start, end) => {
+ const tr = state.tr.deleteRange(start, end);
+ const marks = state.tr.selection.$anchor.nodeBefore?.marks;
+
+ return marks
+ ? Array.from(marks)
+ .filter(m => m.type !== state.schema.marks.user_mark)
+ .reduce((tr, m) => tr.removeStoredMark(m), tr)
+ : tr;
+ }),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // [[<fieldKey> : <Doc>]]
+ // [[:Doc]] => hyperlink
+ // [[fieldKey]] => show field
// [[fieldKey=value]] => show field and also set its value
// [[fieldKey:Doc]] => show field of doc
- new InputRule(
- new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
- (state, match, start, end) => {
- const fieldKey = match[1];
- const rawdocid = match[3];
- const docid = rawdocid ? normalizeEmail((!rawdocid.includes("@") ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1))) : undefined;
- const value = match[2]?.substring(1);
- if (!fieldKey) {
- if (docid) {
- DocServer.GetRefField(docid).then(docx => {
- const rstate = this.TextBox.EditorView?.state;
- const selection = rstate?.selection.$from.pos;
- if (rstate) {
- this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
- }
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid);
- DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to", undefined);
-
- const fstate = this.TextBox.EditorView?.state;
- if (fstate && selection) {
- this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
- }
- });
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
- }
- return state.tr;
- }
- if (value !== "" && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
+ new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
+ const fieldKey = match[1];
+ const rawdocid = match[3];
+ const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined;
+ const value = match[2]?.substring(1);
+ if (!fieldKey) {
+ if (docid) {
+ DocServer.GetRefField(docid).then(docx => {
+ const rstate = this.TextBox.EditorView?.state;
+ const selection = rstate?.selection.$from.pos;
+ if (rstate) {
+ this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+ }
+ const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid);
+ DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined);
+
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate && selection) {
+ this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
+ }
+ });
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
}
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
+ return state.tr;
+ }
+ if (value !== '' && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // wiki:title
+ new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
+ const title = match[1];
+ this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
+
+ this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference');
+
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate) {
+ const tr = fstate?.tr.deleteRange(start, start + 5);
+ return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' ');
+ }
+ return state.tr;
+ }),
- // create an inline view of a document {{ <layoutKey> : <Doc> }}
- // {{:Doc}} => show default view of document
- // {{<layout>}} => show layout for this doc
+ // create an inline view of a document {{ <layoutKey> : <Doc> }}
+ // {{:Doc}} => show default view of document
+ // {{<layout>}} => show layout for this doc
// {{<layout> : Doc}} => show layout for another doc
- new InputRule(
- new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/),
- (state, match, start, end) => {
- const fieldKey = match[1] || "";
- const fieldParam = match[2]?.replace("…", "...") || "";
- const rawdocid = match[3]?.substring(1);
- const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined;
- if (!fieldKey && !docid) return state.tr;
- docid && DocServer.GetRefField(docid).then(docx => {
+ new InputRule(new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), (state, match, start, end) => {
+ const fieldKey = match[1] || '';
+ const fieldParam = match[2]?.replace('…', '...') || '';
+ const rawdocid = match[3]?.substring(1);
+ const docid = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined;
+ if (!fieldKey && !docid) return state.tr;
+ docid &&
+ DocServer.GetRefField(docid).then(docx => {
if (!(docx instanceof Doc && docx)) {
Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docid);
}
});
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
- const sm = state.storedMarks || undefined;
- return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }),
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docid, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() });
+ const sm = state.storedMarks || undefined;
+ 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$/),
- (state, match, start, end) => {
- const tag = match[1];
- if (!tag) return state.tr;
- this.Document[DataSym]["#" + tag] = "#" + tag;
- const tags = StrCast(this.Document[DataSym].tags, ":");
- if (!tags.includes(`#${tag}:`)) {
- this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`;
- }
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
-
+ new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
+ const tag = match[1];
+ if (!tag) return state.tr;
+ this.Document[DataSym]['#' + tag] = '#' + tag;
+ const tags = StrCast(this.Document[DataSym].tags, ':');
+ if (!tags.includes(`#${tag}:`)) {
+ this.Document[DataSym].tags = `${tags + '#' + tag + ':'}`;
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag });
+ return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(' ');
+ }),
// # heading
- textblockTypeInputRule(
- new RegExp(/^(#{1,6})\s$/),
- schema.nodes.heading,
- match => {
- return ({ level: match[1].length });
- }
- ),
+ textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => {
+ return { level: match[1].length };
+ }),
// set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/[ti!x]$/),
- (state, match, start, end) => {
-
- if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
-
- const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
- const node = (state.doc.resolve(start) as any).nodeAfter;
+ new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => {
+ if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+ const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
+ const node = (state.doc.resolve(start) as any).nodeAfter;
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
+ if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- new InputRule(
- new RegExp(/%\(/),
- (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || [];
- const mark = state.schema.marks.summarizeInclusive.create();
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
- sm.push(mark);
- const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
- const content = selected.selection.content();
- const replaced = node ? selected.replaceRangeWith(start, end,
- schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
- state.tr;
+ new InputRule(new RegExp(/%\(/), (state, match, start, end) => {
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const sm = state.storedMarks?.slice() || [];
+ const mark = state.schema.marks.summarizeInclusive.create();
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
- }),
+ sm.push(mark);
+ const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+ const content = selected.selection.content();
+ const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr;
- new InputRule(
- new RegExp(/%\)/),
- (state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
- }),
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+ }),
- ]
+ 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/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index c017db034..01acc3de9 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,35 +1,49 @@
-import { TextSelection } from "prosemirror-state";
-import { Fragment, Node, Slice } from "prosemirror-model";
+import { TextSelection } from 'prosemirror-state';
+import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom';
-import React = require("react");
+import React = require('react');
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
export class SummaryView {
- _fieldWrapper: HTMLSpanElement; // container for label and value
+ dom: HTMLSpanElement; // container for label and value
constructor(node: any, view: any, getPos: any) {
const self = this;
- this._fieldWrapper = document.createElement("span");
- this._fieldWrapper.className = this.className(node.attrs.visibility);
- this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); };
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ this.dom = document.createElement('span');
+ this.dom.className = this.className(node.attrs.visibility);
+ this.dom.onpointerdown = function (e: any) {
+ self.onPointerDown(e, node, view, getPos);
+ };
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
const js = node.toJSON;
- node.toJSON = function () { return js.apply(this, arguments); };
+ node.toJSON = function () {
+ return js.apply(this, arguments);
+ };
- ReactDOM.render(<SummaryViewInternal />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ ReactDOM.render(<SummaryViewInternal />, this.dom);
+ (this as any).dom = this.dom;
}
- className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed');
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
updateSummarizedText(start: any, view: any) {
const mtype = view.state.schema.marks.summarize;
@@ -44,8 +58,7 @@ export class SummaryView {
if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
endPos = i + node.nodeSize - 1;
- }
- else skip = true;
+ } else skip = true;
}
});
}
@@ -56,26 +69,28 @@ export class SummaryView {
const visible = !node.attrs.visibility;
const attrs = { ...node.attrs, visibility: visible };
let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
- if (!visible) { // update summarized text and save in attrs
+ if (!visible) {
+ // update summarized text and save in attrs
textSelection = this.updateSummarizedText(getPos() + 1, view);
attrs.text = textSelection.content();
attrs.textslice = attrs.text.toJSON();
}
- view.dispatch(view.state.tr.
- setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
- replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
- setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
+ view.dispatch(
+ view.state.tr
+ .setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed)
+ .replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it
+ .setNodeMarkup(getPos(), undefined, attrs)
+ ); // update the attrs
e.preventDefault();
e.stopPropagation();
- this._fieldWrapper.className = this.className(visible);
- }
+ this.dom.className = this.className(visible);
+ };
}
-interface ISummaryView {
-}
+interface ISummaryView {}
// currently nothing needs to be rendered for the internal view of a summary.
export class SummaryViewInternal extends React.Component<ISummaryView> {
render() {
return <> </>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 6103a28d6..00c41e187 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,52 +1,117 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { Doc } from "../../../../fields/Doc";
+import React = require('react');
+import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
+import { Doc } from '../../../../fields/Doc';
-
-const emDOM: DOMOutputSpecArray = ["em", 0];
-const strongDOM: DOMOutputSpecArray = ["strong", 0];
-const codeDOM: DOMOutputSpecArray = ["code", 0];
+const emDOM: DOMOutputSpec = ['em', 0];
+const strongDOM: DOMOutputSpec = ['strong', 0];
+const codeDOM: DOMOutputSpec = ['code', 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
splitter: {
attrs: {
- id: { default: "" }
+ id: { default: '' },
},
toDOM(node: any) {
- return ["div", { className: "dummy" }, 0];
- }
+ return ['div', { className: 'dummy' }, 0];
+ },
},
- // :: 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`
+
+ // :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the
+ // published document's title.
+ // NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because
+ // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since
+ // multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to
+ // disambiguate links from one another.
+ // Rendered and parsed as an `<a>`
+ // element.
+ autoLinkAnchor: {
+ attrs: {
+ allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
+ location: { default: null },
+ title: { default: null },
+ },
+ inclusive: false,
+ parseDOM: [
+ {
+ tag: 'a[href]',
+ getAttrs(dom: any) {
+ return {
+ location: dom.getAttribute('location'),
+ title: dom.getAttribute('title'),
+ };
+ },
+ },
+ ],
+ toDOM(node: any) {
+ const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
+ const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
+ return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
+ },
+ },
+ noAutoLinkAnchor: {
+ attrs: {},
+ inclusive: false,
+ parseDOM: [
+ {
+ tag: 'div',
+ getAttrs(dom: any) {
+ return {
+ noAutoLink: dom.getAttribute('data-noAutoLink'),
+ };
+ },
+ },
+ ],
+ toDOM(node: any) {
+ return ['span', { 'data-noAutoLink': 'true' }, 0];
+ },
+ },
+ // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text,
+ // and a title for use in menus and hover. `title`
// defaults to the empty string. Rendered and parsed as an `<a>`
// element.
linkAnchor: {
attrs: {
- allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
+ allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
location: { default: null },
title: { default: null },
- docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
+ docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
},
inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return {
- location: dom.getAttribute("location"),
- title: dom.getAttribute("title")
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'a[href]',
+ getAttrs(dom: any) {
+ return {
+ location: dom.getAttribute('location'),
+ title: dom.getAttribute('title'),
+ };
+ },
+ },
+ ],
toDOM(node: any) {
- const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, "");
- const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, "");
- return node.attrs.docref && node.attrs.title ?
- ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", {
- ...node.attrs,
- class: "prosemirror-attribution",
- href: node.attrs.allAnchors[0].href,
- }, node.attrs.title], ["br"]] :
- //node.attrs.allLinks.length === 1 ?
- ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
+ const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
+ const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
+ return node.attrs.docref && node.attrs.title
+ ? [
+ 'div',
+ ['span', `"`],
+ ['span', 0],
+ ['span', `"`],
+ ['br'],
+ [
+ 'a',
+ {
+ ...node.attrs,
+ class: 'prosemirror-attribution',
+ href: node.attrs.allAnchors[0].href,
+ },
+ node.attrs.title,
+ ],
+ ['br'],
+ ]
+ : //node.attrs.allLinks.length === 1 ?
+ ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
// ["div", { class: "prosemirror-anchor" },
// ["span", { class: "prosemirror-linkBtn" },
// ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
@@ -56,254 +121,273 @@ export const marks: { [index: string]: MarkSpec } = {
// ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
// )]
// ];
- }
+ },
},
/** FONT SIZES */
pFontSize: {
- attrs: { fontSize: { default: "10px" } },
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" };
- }
- }],
- toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]
+ attrs: { fontSize: { default: '10px' } },
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]),
},
/* FONTS */
pFontFamily: {
- attrs: { family: { default: "" } },
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- const cstyle = getComputedStyle(dom);
- if (cstyle.font) {
- if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
- if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
- if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
- if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
- if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
- if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
- }
- }
- }],
- toDOM: (node) => node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]
+ attrs: { family: { default: '' } },
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ const cstyle = getComputedStyle(dom);
+ if (cstyle.font) {
+ if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' };
+ if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' };
+ if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' };
+ if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' };
+ if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' };
+ if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' };
+ }
+ return { family: '' };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]),
},
// :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
pFontColor: {
- attrs: { color: { default: "" } },
+ attrs: { color: { default: '' } },
inclusive: true,
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { color: dom.getAttribute("color") };
- }
- }],
- toDOM: (node) => node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { color: dom.getAttribute('color') };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]),
},
marker: {
attrs: {
- highlight: { default: "transparent" }
+ highlight: { default: 'transparent' },
},
inclusive: true,
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { highlight: dom.getAttribute("backgroundColor") };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { highlight: dom.getAttribute('backgroundColor') };
+ },
+ },
+ ],
toDOM(node: any) {
return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
- }
+ },
},
// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
// Has parse rules that also match `<i>` and `font-style: italic`.
em: {
- parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
- toDOM() { return emDOM; }
+ parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style: italic' }],
+ toDOM() {
+ return emDOM;
+ },
},
// :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
// also match `<b>` and `font-weight: bold`.
strong: {
- parseDOM: [{ tag: "strong" },
- { tag: "b" },
- { style: "font-weight" }],
- toDOM() { return strongDOM; }
+ parseDOM: [{ tag: 'strong' }, { tag: 'b' }, { style: 'font-weight' }],
+ toDOM() {
+ return strongDOM;
+ },
},
strikethrough: {
- parseDOM: [
- { tag: 'strike' },
- { style: 'text-decoration=line-through' },
- { style: 'text-decoration-line=line-through' }
+ parseDOM: [{ tag: 'strike' }, { style: 'text-decoration=line-through' }, { style: 'text-decoration-line=line-through' }],
+ toDOM: () => [
+ 'span',
+ {
+ style: 'text-decoration-line:line-through',
+ },
],
- toDOM: () => ['span', {
- style: 'text-decoration-line:line-through'
- }]
},
subscript: {
excludes: 'superscript',
- parseDOM: [
- { tag: 'sub' },
- { style: 'vertical-align=sub' }
- ],
- toDOM: () => ['sub']
+ parseDOM: [{ tag: 'sub' }, { style: 'vertical-align=sub' }],
+ toDOM: () => ['sub'],
},
superscript: {
excludes: 'subscript',
- parseDOM: [
- { tag: 'sup' },
- { style: 'vertical-align=super' }
- ],
- toDOM: () => ['sup']
+ parseDOM: [{ tag: 'sup' }, { style: 'vertical-align=super' }],
+ toDOM: () => ['sup'],
},
mbulletType: {
attrs: {
- bulletType: { default: "decimal" }
+ bulletType: { default: 'decimal' },
},
toDOM(node: any) {
- return ['span', {
- style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
- }];
- }
+ return [
+ 'span',
+ {
+ style: `background: ${node.attrs.bulletType === 'decimal' ? 'yellow' : node.attrs.bulletType === 'upper-alpha' ? 'blue' : 'green'}`,
+ },
+ ];
+ },
},
metadata: {
toDOM() {
return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
- }
+ },
},
metadataKey: {
toDOM() {
return ['span', { style: 'font-style:italic; ' }];
- }
+ },
},
metadataVal: {
toDOM() {
return ['span'];
- }
+ },
},
summarizeInclusive: {
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline") return null;
- if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
+ if (style.textDecoration === 'underline') return null;
+ if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: solid') !== -1) {
return null;
}
}
return false;
- }
+ },
},
],
inclusive: true,
toDOM() {
- return ['span', {
- style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
- }];
- }
+ return [
+ 'span',
+ {
+ style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)',
+ },
+ ];
+ },
},
summarize: {
inclusive: false,
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline") return null;
- if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+ if (style.textDecoration === 'underline') return null;
+ if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: dotted') !== -1) {
return null;
}
}
return false;
- }
+ },
},
],
toDOM() {
- return ['span', {
- style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
- }];
- }
+ return [
+ 'span',
+ {
+ style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)',
+ },
+ ];
+ },
},
underline: {
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
+ if (style.textDecoration === 'underline' || p.parentElement.outerHTML.indexOf('text-decoration-style:line') !== -1) {
return null;
}
}
return false;
- }
- }
+ },
+ },
// { style: "text-decoration=underline" }
],
- toDOM: () => ['span', {
- style: 'text-decoration:underline;text-decoration-style:line'
- }]
+ toDOM: () => [
+ 'span',
+ {
+ style: 'text-decoration:underline;text-decoration-style:line',
+ },
+ ],
},
search_highlight: {
attrs: {
- selected: { default: false }
+ selected: { default: false },
},
parseDOM: [{ style: 'background: yellow' }],
toDOM(node: any) {
- return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }];
- }
+ return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }];
+ },
},
// the id of the user who entered the text
user_mark: {
attrs: {
- userid: { default: "" },
- modified: { default: "when?" }, // 1 second intervals since 1970
+ userid: { default: '' },
+ modified: { default: 'when?' }, // 1 second intervals since 1970
},
- excludes: "user_mark",
- group: "inline",
+ excludes: 'user_mark',
+ group: 'inline',
toDOM(node: any) {
- const uid = node.attrs.userid.replace(".", "").replace("@", "");
+ const uid = node.attrs.userid.replace('.', '').replace('@', '');
const min = Math.round(node.attrs.modified / 12);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : "";
- return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0];
- }
+ const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
+ return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0];
+ },
},
// the id of the user who entered the text
user_tag: {
attrs: {
- userid: { default: "" },
- modified: { default: "when?" }, // 1 second intervals since 1970
- tag: { default: "" }
+ userid: { default: '' },
+ modified: { default: 'when?' }, // 1 second intervals since 1970
+ tag: { default: '' },
},
- group: "inline",
+ group: 'inline',
inclusive: false,
toDOM(node: any) {
- const uid = node.attrs.userid.replace(".", "").replace("@", "");
- return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0];
- }
+ const uid = node.attrs.userid.replace('.', '').replace('@', '');
+ return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0];
+ },
},
-
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
- parseDOM: [{ tag: "code" }],
- toDOM() { return codeDOM; }
+ parseDOM: [{ tag: 'code' }],
+ toDOM() {
+ return codeDOM;
+ },
},
};
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 2fe0a67cb..5142b7da6 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,15 +1,18 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec";
+import React = require('react');
+import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
+import { listItem, orderedList } from 'prosemirror-schema-list';
+import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
-const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
- preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
+const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
+ hrDOM: DOMOutputSpec = ['hr'],
+ preDOM: DOMOutputSpec = ['pre', ['code', 0]],
+ brDOM: DOMOutputSpec = ['br'],
+ ulDOM: DOMOutputSpec = ['ul', 0];
function formatAudioTime(time: number) {
time = Math.round(time);
const hours = Math.floor(time / 60 / 60);
- const minutes = Math.floor(time / 60) - (hours * 60);
+ const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
@@ -19,67 +22,70 @@ function formatAudioTime(time: number) {
export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec The top level document node.
doc: {
- content: "block+"
+ content: 'block+',
},
paragraph: ParagraphNodeSpec,
audiotag: {
- group: "block",
+ group: 'block',
attrs: {
timeCode: { default: 0 },
- audioId: { default: "" },
- textId: { default: "" }
+ audioId: { default: '' },
+ textId: { default: '' },
},
toDOM(node) {
- return ['audiotag',
+ return [
+ 'audiotag',
{
class: node.attrs.textId,
// style: see FormattedTextBox.scss
- "data-timecode": node.attrs.timeCode,
- "data-audioid": node.attrs.audioId,
- "data-textid": node.attrs.textId,
+ 'data-timecode': node.attrs.timeCode,
+ 'data-audioid': node.attrs.audioId,
+ 'data-textid': node.attrs.textId,
},
- formatAudioTime(node.attrs.timeCode.toString())
+ formatAudioTime(node.attrs.timeCode.toString()),
];
},
parseDOM: [
{
- tag: "audiotag", getAttrs(dom: any) {
+ tag: 'audiotag',
+ getAttrs(dom: any) {
return {
- timeCode: dom.getAttribute("data-timecode"),
- audioId: dom.getAttribute("data-audioid"),
- textId: dom.getAttribute("data-textid")
+ timeCode: dom.getAttribute('data-timecode'),
+ audioId: dom.getAttribute('data-audioid'),
+ textId: dom.getAttribute('data-textid'),
};
- }
+ },
},
- ]
+ ],
},
footnote: {
- group: "inline",
- content: "inline*",
+ group: 'inline',
+ content: 'inline*',
inline: true,
attrs: {
- visibility: { default: false }
+ visibility: { default: false },
},
// This makes the view treat the node as a leaf, even though it
// technically has content
atom: true,
- toDOM: () => ["footnote", 0],
- parseDOM: [{ tag: "footnote" }]
+ toDOM: () => ['footnote', 0],
+ parseDOM: [{ tag: 'footnote' }],
},
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
blockquote: {
- content: "block*",
- group: "block",
+ content: 'block*',
+ group: 'block',
defining: true,
- parseDOM: [{ tag: "blockquote" }],
- toDOM() { return blockquoteDOM; }
+ parseDOM: [{ tag: 'blockquote' }],
+ toDOM() {
+ return blockquoteDOM;
+ },
},
-
// blockquote: {
// ...ParagraphNodeSpec,
// defining: true,
@@ -97,9 +103,11 @@ export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec A horizontal rule (`<hr>`).
horizontal_rule: {
- group: "block",
- parseDOM: [{ tag: "hr" }],
- toDOM() { return hrDOM; }
+ group: 'block',
+ parseDOM: [{ tag: 'hr' }],
+ toDOM() {
+ return hrDOM;
+ },
},
// :: NodeSpec A heading textblock, with a `level` attribute that
@@ -112,12 +120,14 @@ export const nodes: { [index: string]: NodeSpec } = {
level: { default: 1 },
},
defining: true,
- parseDOM: [{ tag: "h1", attrs: { level: 1 } },
- { tag: "h2", attrs: { level: 2 } },
- { tag: "h3", attrs: { level: 3 } },
- { tag: "h4", attrs: { level: 4 } },
- { tag: "h5", attrs: { level: 5 } },
- { tag: "h6", attrs: { level: 6 } }],
+ parseDOM: [
+ { tag: 'h1', attrs: { level: 1 } },
+ { tag: 'h2', attrs: { level: 2 } },
+ { tag: 'h3', attrs: { level: 3 } },
+ { tag: 'h4', attrs: { level: 4 } },
+ { tag: 'h5', attrs: { level: 5 } },
+ { tag: 'h6', attrs: { level: 6 } },
+ ],
toDOM(node) {
const dom = toParagraphDOM(node) as any;
const level = node.attrs.level || 1;
@@ -129,36 +139,38 @@ export const nodes: { [index: string]: NodeSpec } = {
const level = Number(dom.nodeName.substring(1)) || 1;
attrs.level = level;
return attrs;
- }
+ },
},
// :: NodeSpec A code listing. Disallows marks or non-text inline
// nodes by default. Represented as a `<pre>` element with a
// `<code>` element inside of it.
code_block: {
- content: "inline*",
- marks: "_",
- group: "block",
+ content: 'inline*',
+ marks: '_',
+ group: 'block',
code: true,
defining: true,
- parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
- toDOM() { return preDOM; }
+ parseDOM: [{ tag: 'pre', preserveWhitespace: 'full' }],
+ toDOM() {
+ return preDOM;
+ },
},
// :: NodeSpec The text node.
text: {
- group: "inline"
+ group: 'inline',
},
dashComment: {
attrs: {
- docid: { default: "" },
+ docid: { default: '' },
},
inline: true,
- group: "inline",
+ group: 'inline',
toDOM(node) {
const attrs = { style: `width: 40px` };
- return ["span", { ...node.attrs, ...attrs }, "←"];
+ return ['span', { ...node.attrs, ...attrs }, '←'];
},
},
@@ -169,10 +181,10 @@ export const nodes: { [index: string]: NodeSpec } = {
text: { default: undefined },
textslice: { default: undefined },
},
- group: "inline",
+ group: 'inline',
toDOM(node) {
const attrs = { style: `width: 40px` };
- return ["span", { ...node.attrs, ...attrs }];
+ return ['span', { ...node.attrs, ...attrs }];
},
},
@@ -187,27 +199,30 @@ export const nodes: { [index: string]: NodeSpec } = {
width: { default: 100 },
alt: { default: null },
title: { default: null },
- float: { default: "left" },
- location: { default: "add:right" },
- docid: { default: "" }
+ float: { default: 'left' },
+ location: { default: 'add:right' },
+ docid: { default: '' },
},
- group: "inline",
+ group: 'inline',
draggable: true,
- parseDOM: [{
- tag: "img[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt"),
- width: Math.min(100, Number(dom.getAttribute("width"))),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'img[src]',
+ getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute('src'),
+ title: dom.getAttribute('title'),
+ alt: dom.getAttribute('alt'),
+ width: Math.min(100, Number(dom.getAttribute('width'))),
+ };
+ },
+ },
+ ],
// TODO if we don't define toDom, dragging the image crashes. Why?
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}` };
- return ["img", { ...node.attrs, ...attrs }];
- }
+ return ['img', { ...node.attrs, ...attrs }];
+ },
},
dashDoc: {
@@ -216,82 +231,87 @@ export const nodes: { [index: string]: NodeSpec } = {
width: { default: 200 },
height: { default: 100 },
title: { default: null },
- float: { default: "right" },
+ float: { default: 'right' },
hidden: { default: false }, // whether dashComment node has toggle the dashDoc's display off
- fieldKey: { default: "" },
- docid: { default: "" },
- alias: { default: "" }
+ fieldKey: { default: '' },
+ docid: { default: '' },
+ alias: { default: '' },
},
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
dashField: {
inline: true,
attrs: {
- fieldKey: { default: "" },
- docid: { default: "" },
- hideKey: { default: false }
+ fieldKey: { default: '' },
+ docid: { default: '' },
+ hideKey: { default: false },
},
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
equation: {
inline: true,
attrs: {
- fieldKey: { default: "" },
+ fieldKey: { default: '' },
},
atom: true,
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
video: {
inline: true,
attrs: {
src: {},
- width: { default: "100px" },
+ width: { default: '100px' },
alt: { default: null },
- title: { default: null }
+ title: { default: null },
},
- group: "inline",
+ group: 'inline',
draggable: true,
- parseDOM: [{
- tag: "video[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt"),
- width: Math.min(100, Number(dom.getAttribute("width"))),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'video[src]',
+ getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute('src'),
+ title: dom.getAttribute('title'),
+ alt: dom.getAttribute('alt'),
+ width: Math.min(100, Number(dom.getAttribute('width'))),
+ };
+ },
+ },
+ ],
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}` };
- return ["video", { ...node.attrs, ...attrs }];
- }
+ return ['video', { ...node.attrs, ...attrs }];
+ },
},
// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
hard_break: {
inline: true,
- group: "inline",
+ group: 'inline',
selectable: false,
- parseDOM: [{ tag: "br" }],
- toDOM() { return brDOM; }
+ parseDOM: [{ tag: 'br' }],
+ toDOM() {
+ return brDOM;
+ },
},
ordered_list: {
@@ -300,85 +320,108 @@ export const nodes: { [index: string]: NodeSpec } = {
group: 'block',
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: "decimal" },// "decimal", "multi", "bullet"
- fontColor: { default: "inherit" },
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
+ fontColor: { default: 'inherit' },
fontSize: { default: undefined },
fontFamily: { default: undefined },
visibility: { default: true },
- indent: { default: undefined }
+ indent: { default: undefined },
},
parseDOM: [
{
- tag: "ul", getAttrs(dom: any) {
+ tag: 'ul',
+ getAttrs(dom: any) {
return {
- bulletStyle: dom.getAttribute("data-bulletStyle"),
- mapStyle: dom.getAttribute("data-mapStyle"),
+ bulletStyle: dom.getAttribute('data-bulletStyle'),
+ mapStyle: dom.getAttribute('data-mapStyle'),
fontColor: dom.style.color,
- fontSize: dom.style["font-size"],
- fontFamily: dom.style["font-family"],
- indent: dom.style["margin-left"]
+ fontSize: dom.style['font-size'],
+ fontFamily: dom.style['font-family'],
+ indent: dom.style['margin-left'],
};
- }
+ },
},
{
- style: 'list-style-type=disc', getAttrs(dom: any) {
- return { mapStyle: "bullet" };
- }
+ style: 'list-style-type=disc',
+ getAttrs(dom: any) {
+ return { mapStyle: 'bullet' };
+ },
},
{
- tag: "ol", getAttrs(dom: any) {
+ tag: 'ol',
+ getAttrs(dom: any) {
return {
- bulletStyle: dom.getAttribute("data-bulletStyle"),
- mapStyle: dom.getAttribute("data-mapStyle"),
+ bulletStyle: dom.getAttribute('data-bulletStyle'),
+ mapStyle: dom.getAttribute('data-mapStyle'),
fontColor: dom.style.color,
- fontSize: dom.style["font-size"],
- fontFamily: dom.style["font-family"],
- indent: dom.style["margin-left"]
+ fontSize: dom.style['font-size'],
+ fontFamily: dom.style['font-family'],
+ indent: dom.style['margin-left'],
};
- }
- }],
- toDOM(node: Node<any>) {
- const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
- const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : "";
- const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : "";
- const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : "";
- const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : "";
- if (node.attrs.mapStyle === "bullet") {
- return ['ul', {
- "data-mapStyle": node.attrs.mapStyle,
- "data-bulletStyle": node.attrs.bulletStyle,
- style: `${fsize} ${ffam} ${fcol} ${marg}`
- }, 0];
+ },
+ },
+ ],
+ toDOM(node: Node) {
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
+ const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : '';
+ const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : '';
+ const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : '';
+ const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
+ if (node.attrs.mapStyle === 'bullet') {
+ return [
+ 'ul',
+ {
+ 'data-mapStyle': node.attrs.mapStyle,
+ 'data-bulletStyle': node.attrs.bulletStyle,
+ style: `${fsize} ${ffam} ${fcol} ${marg}`,
+ },
+ 0,
+ ];
}
- return node.attrs.visibility ?
- ['ol', {
- class: `${map}-ol`,
- "data-mapStyle": node.attrs.mapStyle,
- "data-bulletStyle": node.attrs.bulletStyle,
- style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`
- }, 0] :
- ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
- }
+ return node.attrs.visibility
+ ? [
+ 'ol',
+ {
+ class: `${map}-ol`,
+ 'data-mapStyle': node.attrs.mapStyle,
+ 'data-bulletStyle': node.attrs.bulletStyle,
+ style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`,
+ },
+ 0,
+ ]
+ : ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+ },
},
list_item: {
...listItem,
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet"
- visibility: { default: true }
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
+ visibility: { default: true },
},
content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)',
- parseDOM: [{
- tag: "li", getAttrs(dom: any) {
- return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'li',
+ getAttrs(dom: any) {
+ return { mapStyle: dom.getAttribute('data-mapStyle'), bulletStyle: dom.getAttribute('data-bulletStyle') };
+ },
+ },
+ ],
toDOM(node: any) {
- const map = node.attrs.bulletStyle ? node.attrs.mapStyle + 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}...`]];
- }
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + 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/schema_rts.ts b/src/client/views/nodes/formattedText/schema_rts.ts
index 83561073c..d6e0e6002 100644
--- a/src/client/views/nodes/formattedText/schema_rts.ts
+++ b/src/client/views/nodes/formattedText/schema_rts.ts
@@ -1,8 +1,7 @@
-import { Schema, Slice } from "prosemirror-model";
-
-import { nodes } from "./nodes_rts";
-import { marks } from "./marks_rts";
+import { Schema, Slice } from 'prosemirror-model';
+import { nodes } from './nodes_rts';
+import { marks } from './marks_rts';
// :: Schema
// This schema rougly corresponds to the document schema used by
@@ -20,7 +19,9 @@ const fromJson = schema.nodeFromJSON;
schema.nodeFromJSON = (json: any) => {
const node = fromJson(json);
if (json.type === schema.nodes.summary.name) {
- node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
+ // bcz: this is a hacky way to convert the JSON that's serialized for a summary node into the Slice that the summary node wants at run-time.
+ // since attrs are readonly, assigning the text field like this violates the way prosemirror works, but I think we can get away with it.
+ (node.attrs.text as any) = Slice.fromJSON(schema, node.attrs.textslice);
}
return node;
-}; \ No newline at end of file
+};
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 06932d145..a0a2dd4f8 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1078,11 +1078,13 @@
.miniPres {
cursor: grab;
position: absolute;
- right: 10;
- top: 10;
+ top: 0;
+ left: 0;
opacity: 0.1;
transition: all 0.4s;
color: $white;
+ width: 100%;
+ height: 100%;
}
.miniPres:hover {
@@ -1101,7 +1103,6 @@
align-items: center;
display: flex;
position: absolute;
- right: 10px;
transition: all 0.2s;
.presPanel-button-text {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 8a0d03ae8..6e5eb3300 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,79 +1,93 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { ColorState, SketchPicker } from "react-color";
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { ColorState, SketchPicker } from 'react-color';
import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
-import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../../fields/Doc";
-import { InkTool } from "../../../../fields/InkField";
-import { List } from "../../../../fields/List";
-import { PrefetchProxy } from "../../../../fields/Proxy";
-import { listSpec } from "../../../../fields/Schema";
-import { ScriptField } from "../../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnFalse, returnOne, returnTrue } from '../../../../Utils';
-import { Docs } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { CollectionDockingView } from "../../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../../collections/CollectionView";
-import { TabDocView } from "../../collections/TabDocView";
-import { ViewBoxBaseComponent } from "../../DocComponent";
-import { Colors } from "../../global/globalEnums";
-import { LightboxView } from "../../LightboxView";
-import { CollectionFreeFormDocumentView } from "../CollectionFreeFormDocumentView";
+import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc';
+import { InkTool } from '../../../../fields/InkField';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { CollectionDockingView } from '../../collections/CollectionDockingView';
+import { MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionView } from '../../collections/CollectionView';
+import { TabDocView } from '../../collections/TabDocView';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { Colors } from '../../global/globalEnums';
+import { LightboxView } from '../../LightboxView';
+import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
-import "./PresBox.scss";
-import { PresEffect, PresMovement, PresStatus } from "./PresEnums";
+import './PresBox.scss';
+import { PresEffect, PresMovement, PresStatus } from './PresEnums';
-export class PinProps {
+export interface PinProps {
audioRange?: boolean;
- unpin?: boolean;
setPosition?: boolean;
hidePresBox?: boolean;
+ pinWithView?: PinViewProps;
+ pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document
+ panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area)
+ panelHeight?: number;
+}
+
+export interface PinViewProps {
+ bounds: MarqueeViewBounds;
+ scale: number;
}
@observer
export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(PresBox, fieldKey);
+ }
/**
* transitions & effects for documents
* @param renderDoc
* @param layoutDoc
*/
- static renderEffectsDoc(renderDoc: any, layoutDoc: Doc) {
+ static renderEffectsDoc(renderDoc: any, layoutDoc: Doc, presDoc: Doc) {
const effectProps = {
- left: layoutDoc.presEffectDirection === PresEffect.Left,
- right: layoutDoc.presEffectDirection === PresEffect.Right,
- top: layoutDoc.presEffectDirection === PresEffect.Top,
- bottom: layoutDoc.presEffectDirection === PresEffect.Bottom,
+ left: presDoc.presEffectDirection === PresEffect.Left,
+ right: presDoc.presEffectDirection === PresEffect.Right,
+ top: presDoc.presEffectDirection === PresEffect.Top,
+ bottom: presDoc.presEffectDirection === PresEffect.Bottom,
opposite: true,
- delay: layoutDoc.presTransition,
+ delay: presDoc.presTransition,
// when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc,
};
- switch (layoutDoc.presEffect) {
- case PresEffect.Zoom: return (<Zoom {...effectProps}>{renderDoc}</Zoom>);
- case PresEffect.Fade: return (<Fade {...effectProps}>{renderDoc}</Fade>);
- case PresEffect.Flip: return (<Flip {...effectProps}>{renderDoc}</Flip>);
- case PresEffect.Rotate: return (<Rotate {...effectProps}>{renderDoc}</Rotate>);
- case PresEffect.Bounce: return (<Bounce {...effectProps}>{renderDoc}</Bounce>);
- case PresEffect.Roll: return (<Roll {...effectProps}>{renderDoc}</Roll>);
- case PresEffect.Lightspeed: return (<LightSpeed {...effectProps}>{renderDoc}</LightSpeed>);
+ switch (presDoc.presEffect) {
+ case PresEffect.Zoom:
+ return <Zoom {...effectProps}>{renderDoc}</Zoom>;
+ case PresEffect.Fade:
+ return <Fade {...effectProps}>{renderDoc}</Fade>;
+ case PresEffect.Flip:
+ return <Flip {...effectProps}>{renderDoc}</Flip>;
+ case PresEffect.Rotate:
+ return <Rotate {...effectProps}>{renderDoc}</Rotate>;
+ case PresEffect.Bounce:
+ return <Bounce {...effectProps}>{renderDoc}</Bounce>;
+ case PresEffect.Roll:
+ return <Roll {...effectProps}>{renderDoc}</Roll>;
+ case PresEffect.Lightspeed:
+ return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
case PresEffect.None:
- default: return renderDoc;
+ default:
+ return renderDoc;
}
}
public static EffectsProvider(layoutDoc: Doc, renderDoc: any) {
- return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ?
- PresBox.renderEffectsDoc(renderDoc, layoutDoc)
- :
- renderDoc;
+ return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) : renderDoc;
}
@observable public static Instance: PresBox;
@@ -90,13 +104,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _expandBoolean: boolean = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
+
+ @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView
@observable private transitionTools: boolean = false;
@observable private newDocumentTools: boolean = false;
@observable private progressivizeTools: boolean = false;
@observable private openMovementDropdown: boolean = false;
@observable private openEffectDropdown: boolean = false;
@observable private presentTools: boolean = false;
- @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
+ @computed get isTreeOrStack() {
+ return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any);
+ }
+ @computed get isTree() {
+ return this.layoutDoc._viewType === CollectionViewType.Tree;
+ }
+ @computed get presFieldKey() {
+ return StrCast(this.layoutDoc.presFieldKey, 'data');
+ }
+ @computed get childDocs() {
+ return DocListCast(this.rootDoc[this.presFieldKey]);
+ }
+ @observable _treeViewMap: Map<Doc, number> = new Map();
+
@computed get tagDocs() {
const tagDocs: Doc[] = [];
for (const doc of this.childDocs) {
@@ -105,11 +134,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
return tagDocs;
}
- @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); }
- @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); }
- @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); }
+ @computed get itemIndex() {
+ return NumCast(this.rootDoc._itemIndex);
+ }
+ @computed get activeItem() {
+ return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null);
+ }
+ @computed get targetDoc() {
+ return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
+ }
@computed get scrollable(): boolean {
- //TODO: likely do NOT have to update this for note-taking view, but still worth putting here
if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
else return false;
}
@@ -117,21 +151,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true;
else return false;
}
- @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); }
constructor(props: any) {
super(props);
- if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this);
- if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations.
- Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
- title: "pres element template", type: DocumentType.PRESELEMENT, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
- }));
- // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement
- // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent
- // the preselement docs from being part of multiple presentations since they would all have the same field, or we'd have to keep per-presentation data
- // stored on each pres element.
- (this.presElement as Doc).lookupField = ScriptField.MakeFunction("lookupPresBoxField(container, field, data)",
- { field: "string", data: Doc.name, container: Doc.name });
- }
+ if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this));
this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
}
@computed get selectedDocumentView() {
@@ -139,19 +161,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc);
}
@computed get isPres(): boolean {
- document.removeEventListener("keydown", PresBox.keyEventsWrapper, true);
+ document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
if (this.selectedDoc?.type === DocumentType.PRES) {
- document.removeEventListener("keydown", PresBox.keyEventsWrapper, true);
- document.addEventListener("keydown", PresBox.keyEventsWrapper, true);
+ document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
+ document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
return true;
}
return false;
}
- @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get selectedDoc() {
+ return this.selectedDocumentView?.rootDoc;
+ }
+ _unmounting = false;
@action
componentWillUnmount() {
- document.removeEventListener("keydown", PresBox.keyEventsWrapper, true);
+ this._unmounting = true;
+ document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
this._presKeyEventsActive = false;
this.resetPresentation();
// Turn of progressivize editors
@@ -161,38 +187,39 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
componentDidMount() {
- this.rootDoc.presBox = this.rootDoc;
- this.rootDoc._forceRenderEngine = "timeline";
+ this._unmounting = false;
+ this.rootDoc._forceRenderEngine = 'timeline';
this.layoutDoc.presStatus = PresStatus.Edit;
this.layoutDoc._gridGap = 0;
this.layoutDoc._yMargin = 0;
this.turnOffEdit(true);
- DocListCastAsync((Doc.UserDoc().myTrails as Doc).data).then(pres =>
- !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.UserDoc().myTrails as Doc, "data", this.rootDoc));
- this._disposers.selection = reaction(() => SelectionManager.Views(),
- views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation());
+ DocListCastAsync(Doc.MyTrails.data).then(pres => !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.MyTrails, 'data', this.rootDoc));
+ this._disposers.selection = reaction(
+ () => SelectionManager.Views(),
+ views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()
+ );
}
@action
updateCurrentPresentation = (pres?: Doc) => {
- if (pres) Doc.UserDoc().activePresentation = pres;
- else Doc.UserDoc().activePresentation = this.rootDoc;
- document.removeEventListener("keydown", PresBox.keyEventsWrapper, true);
- document.addEventListener("keydown", PresBox.keyEventsWrapper, true);
+ if (pres) Doc.ActivePresentation = pres;
+ else Doc.ActivePresentation = this.rootDoc;
+ document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
+ document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
this._presKeyEventsActive = true;
PresBox.Instance = this;
- }
+ };
// There are still other internal frames and should go through all frames before going to next slide
nextInternalFrame = (targetDoc: Doc, activeItem: Doc) => {
- const currentFrame = Cast(targetDoc?._currentFrame, "number", null);
+ const currentFrame = Cast(targetDoc?._currentFrame, 'number', null);
const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
- targetDoc._viewTransition = "all 1s";
- setTimeout(() => targetDoc._viewTransition = undefined, 1010);
+ targetDoc._viewTransition = 'all 1s';
+ setTimeout(() => (targetDoc._viewTransition = undefined), 1010);
this.nextKeyframe(targetDoc, activeItem);
if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc);
else targetDoc.keyFrameEditing = true;
- }
+ };
_mediaTimer!: [NodeJS.Timeout, Doc];
// 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played
@@ -202,44 +229,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.presStartTime), NumCast(activeItem.presStartTime) + duration);
}
- // if (targetDoc.type === DocumentType.AUDIO) {
- // if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
- // targetDoc._triggerAudio = NumCast(activeItem.presStartTime);
- // this._mediaTimer = [setTimeout(() => targetDoc._audioStop = true, duration * 1000), targetDoc];
- // } else if (targetDoc.type === DocumentType.VID) {
- // targetDoc._triggerVideoStop = true;
- // setTimeout(() => targetDoc._currentTimecode = NumCast(activeItem.presStartTime), 10);
- // setTimeout(() => targetDoc._triggerVideo = true, 20);
- // this._mediaTimer = [setTimeout(() => targetDoc._triggerVideoStop = true, (duration * 1000) + 20), targetDoc];
- // }
- }
+ };
stopTempMedia = (targetDocField: FieldResult) => {
const targetDoc = Cast(targetDocField, Doc, null);
- if (targetDoc?.type === DocumentType.AUDIO) {
- if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
- targetDoc._audioStop = true;
- } else if (targetDoc?.type === DocumentType.VID) {
- if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
- targetDoc._triggerVideoStop = true;
+ if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
+ const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
+ targMedia?.ComponentView?.Pause?.();
}
- }
+ };
- //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
- // No more frames in current doc and next slide is defined, therefore move to next slide
+ //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
+ // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
+ // No more frames in current doc and next slide is defined, therefore move to next slide
nextSlide = (activeNext: Doc) => {
const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null);
+ console.info('nextSlide', activeNext.title, targetNext?.title);
let nextSelected = this.itemIndex + 1;
this.gotoDocument(nextSelected, this.activeItem);
for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) {
if (!this.childDocs[nextSelected].groupWithUp) {
break;
} else {
- console.log("Title: " + this.childDocs[nextSelected].title);
+ console.log('Title: ' + this.childDocs[nextSelected].title);
this.gotoDocument(nextSelected, this.activeItem, true);
}
}
- }
+ };
// Called when the user activates 'next' - to move to the next part of the pres. trail
@action
@@ -247,7 +263,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null);
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- const lastFrame = Cast(targetDoc?.lastFrame, "number", null);
+ const lastFrame = Cast(targetDoc?.lastFrame, 'number', null);
const curFrame = NumCast(targetDoc?._currentFrame);
let internalFrames: boolean = false;
if (activeItem.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true;
@@ -255,13 +271,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Case 1: There are still other frames and should go through all frames before going to next slide
this.nextInternalFrame(targetDoc, activeItem);
} else if (this.childDocs[this.itemIndex + 1] !== undefined) {
- // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide
+ // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide
this.nextSlide(activeNext);
} else if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) {
// Case 3: Last slide and presLoop is toggled ON or it is in Edit mode
this.gotoDocument(0, this.activeItem);
}
- }
+ };
// Called when the user activates 'back' - to move to the previous part of the pres. trail
@action
@@ -270,7 +286,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetDoc: Doc = this.targetDoc;
const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null);
const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null);
- const lastFrame = Cast(targetDoc.lastFrame, "number", null);
+ const lastFrame = Cast(targetDoc.lastFrame, 'number', null);
const curFrame = NumCast(targetDoc._currentFrame);
let prevSelected = this.itemIndex;
// Functionality for group with up
@@ -290,7 +306,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// Case 3: Pres loop is on so it should go to the last slide
this.gotoDocument(this.childDocs.length - 1, activeItem);
}
- }
+ };
//The function that is called when a document is clicked or reached through next or back.
//it'll also execute the necessary actions if presentation is playing.
@@ -303,26 +319,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
}
- if (from?.mediaStop === "auto" && this.layoutDoc.presStatus !== PresStatus.Edit) {
+ if (from?.mediaStop === 'auto' && this.layoutDoc.presStatus !== PresStatus.Edit) {
this.stopTempMedia(from.presentationTargetDoc);
}
// If next slide is audio / video 'Play automatically' then the next slide should be played
- if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && (activeItem.mediaStart === "auto")) {
+ if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') {
this.startTempMedia(targetDoc, activeItem);
}
if (targetDoc) {
- targetDoc && runInAction(() => {
- if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0;
- else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500;
- });
- setTimeout(() => targetDoc.focusSpeed = 500, this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510);
+ Doc.linkFollowHighlight(targetDoc.annotationOn instanceof Doc ? [targetDoc, targetDoc.annotationOn] : targetDoc);
+ targetDoc &&
+ runInAction(() => {
+ if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0;
+ else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500;
+ });
+ setTimeout(() => (targetDoc.focusSpeed = 500), this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510);
}
if (targetDoc?.lastFrame !== undefined) {
targetDoc._currentFrame = 0;
}
if (!group) this._selectedArray.clear();
this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array
- if (this.layoutDoc._viewType === "stacking" && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
+ this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
this.onHideDocument(); //Handles hide after/before
}
});
@@ -331,40 +349,57 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
navigateToView = (targetDoc: Doc, activeItem: Doc) => {
clearTimeout(this._navTimer);
const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
- bestTarget && runInAction(() => {
- if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
- bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
- bestTarget._scrollTop = activeItem.presPinViewScroll;
- } else if (bestTarget.type === DocumentType.COMPARISON) {
- bestTarget._clipWidth = activeItem.presPinClipWidth;
- } else if (bestTarget.type === DocumentType.VID) {
- bestTarget._currentTimecode = activeItem.presPinTimecode;
+ if (bestTarget) console.log(bestTarget.title, bestTarget.type);
+ else console.log('no best target');
+ if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false);
+ };
+
+ // navigates to the bestTarget document by making sure it is on screen,
+ // then it applies the view specs stored in activeItem to
+ @action
+ static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
+ if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
+ bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
+ bestTarget._scrollTop = activeItem.presPinViewScroll;
+ } else if (bestTarget.type === DocumentType.COMPARISON) {
+ bestTarget._clipWidth = activeItem.presPinClipWidth;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) {
+ bestTarget._currentTimecode = activeItem.presStartTime;
+ } else {
+ const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
+ bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
+ if (contentBounds) {
+ bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2;
+ bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2;
+ const dv = DocumentManager.Instance.getDocumentView(bestTarget);
+ if (dv) {
+ bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0]));
+ }
} else {
- bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
bestTarget._panX = activeItem.presPinViewX;
bestTarget._panY = activeItem.presPinViewY;
bestTarget._viewScale = activeItem.presPinViewScale;
}
- this._navTimer = setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510);
- });
+ }
+ return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510);
}
/**
* This method makes sure that cursor navigates to the element that
- * has the option open and last in the group.
- * Design choice: If the next document is not in presCollection or
+ * has the option open and last in the group.
+ * Design choice: If the next document is not in presCollection or
* presCollection itself then if there is a presCollection it will add
* a new tab. If presCollection is undefined it will open the document
- * on the right.
+ * on the right.
*/
navigateToElement = async (curDoc: Doc) => {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- const srcContext = Cast(targetDoc.context, Doc, null);
+ const srcContext = Cast(targetDoc.context, Doc, null) ?? Cast(Cast(targetDoc.annotationOn, Doc, null)?.context, Doc, null);
const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined;
const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc);
- const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
+ const tab = CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext);
this.turnOffEdit();
// Handles the setting of presCollection
if (includesDoc) {
@@ -388,7 +423,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
self._eleArray.splice(0, self._eleArray.length, ...eleViewCache);
});
const openInTab = (doc: Doc, finished?: () => void) => {
- collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ":left");
+ collectionDocView ? collectionDocView.props.addDocTab(doc, '') : this.props.addDocTab(doc, '');
this.layoutDoc.presCollection = targetDoc;
// this still needs some fixing
setTimeout(resetSelection, 500);
@@ -401,21 +436,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// If openDocument is selected then it should open the document for the user
if (activeItem.openDocument) {
LightboxView.SetLightboxDoc(targetDoc);
+ // openInTab(targetDoc);
} else if (curDoc.presMovement === PresMovement.Pan && targetDoc) {
LightboxView.SetLightboxDoc(undefined);
- await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
+ await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
} else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
LightboxView.SetLightboxDoc(undefined);
- //awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
+ //awaiting jump so that new scale can be found, since jumping is async
+ await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
}
// After navigating to the document, if it is added as a presPinView then it will
// adjust the pan and scale to that of the pinView when it was added.
if (activeItem.presPinView) {
+ console.log(targetDoc.title);
+ console.log('presPinView in PresBox.tsx:420');
// if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
this.navigateToView(targetDoc, activeItem);
}
- }
+ };
/**
* Uses the viewfinder to progressivize through the different views of a single collection.
@@ -425,10 +463,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetDoc: Doc = this.targetDoc;
const srcContext = Cast(targetDoc?.context, Doc, null);
const docView = DocumentManager.Instance.getDocumentView(targetDoc);
- const vfLeft = this.checkList(targetDoc, activeItem["viewfinder-left-indexed"]);
- const vfWidth = this.checkList(targetDoc, activeItem["viewfinder-width-indexed"]);
- const vfTop = this.checkList(targetDoc, activeItem["viewfinder-top-indexed"]);
- const vfHeight = this.checkList(targetDoc, activeItem["viewfinder-height-indexed"]);
+ const vfLeft = this.checkList(targetDoc, activeItem['viewfinder-left-indexed']);
+ const vfWidth = this.checkList(targetDoc, activeItem['viewfinder-width-indexed']);
+ const vfTop = this.checkList(targetDoc, activeItem['viewfinder-top-indexed']);
+ const vfHeight = this.checkList(targetDoc, activeItem['viewfinder-height-indexed']);
// Case 1: document that is not a Golden Layout tab
if (srcContext) {
const srcDocView = DocumentManager.Instance.getDocumentView(srcContext);
@@ -439,8 +477,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const newPanX = NumCast(targetDoc.x) + NumCast(layoutdoc._width) / 2;
const newPanY = NumCast(targetDoc.y) + NumCast(layoutdoc._height) / 2;
const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight);
- srcContext._panX = newPanX + (vfLeft + (vfWidth / 2));
- srcContext._panY = newPanY + (vfTop + (vfHeight / 2));
+ srcContext._panX = newPanX + (vfLeft + vfWidth / 2);
+ srcContext._panY = newPanY + (vfTop + vfHeight / 2);
srcContext._viewScale = newScale;
}
}
@@ -449,8 +487,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const panelWidth: number = docView.props.PanelWidth();
const panelHeight: number = docView.props.PanelHeight();
const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight);
- targetDoc._panX = vfLeft + (vfWidth / 2);
- targetDoc._panY = vfTop + (vfWidth / 2);
+ targetDoc._panX = vfLeft + vfWidth / 2;
+ targetDoc._panY = vfTop + vfWidth / 2;
targetDoc._viewScale = newScale;
}
const resize = document.getElementById('resizable');
@@ -460,7 +498,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
resize.style.top = vfTop + 'px';
resize.style.left = vfLeft + 'px';
}
- }
+ };
/**
* For 'Hide Before' and 'Hide After' buttons making sure that
@@ -474,18 +512,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (tagDoc) tagDoc.opacity = 1;
const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
const curInd: number = itemIndexes.indexOf(index);
- if (tagDoc === this.layoutDoc.presCollection) { tagDoc.opacity = 1; }
- else {
- if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { }
- else if (curDoc.presHideBefore) {
+ if (tagDoc === this.layoutDoc.presCollection) {
+ tagDoc.opacity = 1;
+ } else {
+ if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) {
+ } else if (curDoc.presHideBefore) {
if (index > this.itemIndex) {
tagDoc.opacity = 0;
} else if (!curDoc.presHideAfter) {
tagDoc.opacity = 1;
}
}
- if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== (itemIndexes.length - 1)) { }
- else if (curDoc.presHideAfter) {
+ if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) {
+ } else if (curDoc.presHideAfter) {
if (index < this.itemIndex) {
tagDoc.opacity = 0;
} else if (!curDoc.presHideBefore) {
@@ -494,9 +533,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
});
- }
-
-
+ };
//The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc
@action
@@ -505,13 +542,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
let activeItem: Doc = this.activeItem;
let targetDoc: Doc = this.targetDoc;
let duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition);
- const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms));
- const load = async () => { // Wrap the loop into an async function for this to work
+ const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms)));
+ const load = async () => {
+ // Wrap the loop into an async function for this to work
for (var i = startSlide; i < this.childDocs.length; i++) {
activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null);
duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition);
- if (duration < 100) { duration = 2500; }
+ if (duration < 100) {
+ duration = 2500;
+ }
if (NumCast(targetDoc.lastFrame) > 0) {
for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) {
await timer(duration / NumCast(targetDoc.lastFrame));
@@ -519,7 +559,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
- await timer(duration); this.next(); // then the created Promise can be awaited
+ await timer(duration);
+ this.next(); // then the created Promise can be awaited
if (i === this.childDocs.length - 1) {
setTimeout(() => {
clearTimeout(this._presTimer);
@@ -533,7 +574,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.startPresentation(startSlide);
this.gotoDocument(startSlide, activeItem);
load();
- }
+ };
// The function pauses the auto presentation
@action
@@ -544,20 +585,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.layoutDoc.presLoop = false;
this.childDocs.forEach(this.stopTempMedia);
}
- }
+ };
//The function that resets the presentation by removing every action done by it. It also
//stops the presentaton.
resetPresentation = () => {
this.rootDoc._itemIndex = 0;
- this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)).filter(doc => doc instanceof Doc).forEach(doc => {
- try {
- doc.opacity = 1;
- } catch (e) {
- console.log("Reset presentation error: ", e);
- }
- });
- }
+ this.childDocs
+ .map(doc => Cast(doc.presentationTargetDoc, Doc, null))
+ .filter(doc => doc instanceof Doc)
+ .forEach(doc => {
+ try {
+ doc.opacity = 1;
+ } catch (e) {
+ console.log('Reset presentation error: ', e);
+ }
+ });
+ };
// The function allows for viewing the pres path on toggle
@action togglePath = (srcContext: Doc, off?: boolean) => {
@@ -565,19 +609,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._pathBoolean = false;
srcContext.presPathView = false;
} else {
- runInAction(() => this._pathBoolean = !this._pathBoolean);
+ runInAction(() => (this._pathBoolean = !this._pathBoolean));
srcContext.presPathView = this._pathBoolean;
}
- }
+ };
// The function allows for expanding the view of pres on toggle
@action toggleExpandMode = () => {
- runInAction(() => this._expandBoolean = !this._expandBoolean);
+ runInAction(() => (this._expandBoolean = !this._expandBoolean));
this.rootDoc.expandBoolean = this._expandBoolean;
- this.childDocs.forEach((doc) => {
+ this.childDocs.forEach(doc => {
doc.presExpandInlineButton = this._expandBoolean;
});
- }
+ };
/**
* The function that starts the presentation at the given index, also checking if actions should be applied
@@ -595,48 +639,45 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
tagDoc.opacity = 0;
}
});
- }
+ };
/**
* The method called to open the presentation as a minimized view
*/
@action
- updateMinimize = () => {
- const docView = DocumentManager.Instance.getDocumentView(this.layoutDoc);
- if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- console.log("case 1");
+ updateMinimize = async () => {
+ if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
this.layoutDoc.presStatus = PresStatus.Edit;
- Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
- CollectionDockingView.AddSplit(this.rootDoc, "right");
- } else if ((true || this.layoutDoc.context) && docView) {
- console.log("case 2");
+ Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc);
+ CollectionDockingView.AddSplit(this.rootDoc, 'right');
+ } else {
this.layoutDoc.presStatus = PresStatus.Edit;
clearTimeout(this._presTimer);
const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250);
this.rootDoc.y = pt[1] + 10;
- this.rootDoc._height = 35;
- this.rootDoc._width = 250;
- docView.props.removeDocument?.(this.layoutDoc);
- Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
- } else {
- console.log("case 3");
- // TODO glr: fix this case
+ this.rootDoc._height = 30;
+ this.rootDoc._width = 248;
+ Doc.AddDocToList(Doc.MyOverlayDocs, undefined, this.rootDoc);
+ this.props.removeDocument?.(this.layoutDoc);
}
- }
+ };
/**
* Called when the user changes the view type
* Either 'List' (stacking) or 'Slides' (carousel)
*/
- // @undoBatch
+ @undoBatch
viewChanged = action((e: React.ChangeEvent) => {
//@ts-ignore
const viewType = e.target.selectedOptions[0].value as CollectionViewType;
+ this.layoutDoc.presFieldKey = this.fieldKey + (viewType === CollectionViewType.Tree ? '-linearized' : '');
// pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
- viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined);
+ [CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined);
this.rootDoc._viewType = viewType;
- if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 0;
+ if (this.isTreeOrStack) {
+ this.layoutDoc._gridGap = 0;
+ }
});
/**
@@ -664,20 +705,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
});
-
setMovementName = action((movement: any, activeItem: Doc): string => {
let output: string = 'none';
switch (movement) {
- case PresMovement.Zoom: output = 'Pan & Zoom'; break; //Pan and zoom
- case PresMovement.Pan: output = 'Pan'; break; //Pan
- case PresMovement.Jump: output = 'Jump cut'; break; //Jump Cut
- case PresMovement.None: output = 'None'; break; //None
- default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom
+ case PresMovement.Zoom:
+ output = 'Pan & Zoom';
+ break; //Pan and zoom
+ case PresMovement.Pan:
+ output = 'Pan';
+ break; //Pan
+ case PresMovement.Jump:
+ output = 'Jump cut';
+ break; //Jump Cut
+ case PresMovement.None:
+ output = 'None';
+ break; //None
+ default:
+ output = 'Zoom';
+ activeItem.presMovement = 'zoom';
+ break; //default set as zoom
}
return output;
});
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isChildActive = isActive));
+ whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
// For dragging documents into the presentation trail
addDocumentFilter = (docs: Doc[]) => {
docs.forEach((doc, i) => {
@@ -685,8 +736,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (doc.type === DocumentType.LABEL) {
const audio = Cast(doc.annotationOn, Doc, null);
if (audio) {
- audio.mediaStart = "manual";
- audio.mediaStop = "manual";
+ audio.mediaStart = 'manual';
+ audio.mediaStop = 'manual';
audio.presStartTime = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
audio.presEndTime = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
audio.presDuration = audio.presStartTime - audio.presEndTime;
@@ -701,7 +752,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
setTimeout(() => this.removeDocument(doc), 0);
return false;
} else {
- if (!doc.presentationTargetDoc) doc.title = doc.title + " - Slide";
+ if (!doc.presentationTargetDoc) doc.title = doc.title + ' - Slide';
doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf);
doc.presMovement = PresMovement.Zoom;
if (this._expandBoolean) doc.presExpandInlineButton = true;
@@ -709,13 +760,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
});
return true;
- }
- childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
- removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
- getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
+ };
+ childLayoutTemplate = () => (!this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement));
+ removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
panelHeight = () => this.props.PanelHeight() - 40;
- isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && this.props.layerProvider?.(this.layoutDoc) !== false) &&
- (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+ isContentActive = (outsideReaction?: boolean) =>
+ Doc.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false;
/**
* For sorting the array so that the order is maintained when it is dropped.
@@ -723,7 +774,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
sortArray = (): Doc[] => {
return this.childDocs.filter(doc => this._selectedArray.has(doc));
- }
+ };
/**
* Method to get the list of selected items in the order in which they have been selected
@@ -732,9 +783,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const list = Array.from(this._selectedArray.keys()).map((doc: Doc, index: any) => {
const curDoc = Cast(doc, Doc, null);
const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
- if (curDoc && curDoc === this.activeItem) return <div key={index} className="selectedList-items"><b>{index + 1}. {curDoc.title}</b></div>;
- else if (tagDoc) return <div key={index} className="selectedList-items">{index + 1}. {curDoc.title}</div>;
- else if (curDoc) return <div key={index} className="selectedList-items">{index + 1}. {curDoc.title}</div>;
+ if (curDoc && curDoc === this.activeItem)
+ return (
+ <div key={index} className="selectedList-items">
+ <b>
+ {index + 1}. {curDoc.title}
+ </b>
+ </div>
+ );
+ else if (tagDoc)
+ return (
+ <div key={index} className="selectedList-items">
+ {index + 1}. {curDoc.title}
+ </div>
+ );
+ else if (curDoc)
+ return (
+ <div key={index} className="selectedList-items">
+ {index + 1}. {curDoc.title}
+ </div>
+ );
});
return list;
}
@@ -743,7 +811,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
selectPres = () => {
const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc);
presDocView && SelectionManager.SelectView(presDocView, false);
- }
+ };
//Regular click
@action
@@ -753,12 +821,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
else this.updateCurrentPresentation(context);
- if (this.activeItem.setPosition &&
- this.activeItem.y !== undefined &&
- this.activeItem.x !== undefined &&
- this.targetDoc.x !== undefined &&
- this.targetDoc.y !== undefined) {
- const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms));
+ if (this.activeItem.setPosition && this.activeItem.y !== undefined && this.activeItem.x !== undefined && this.targetDoc.x !== undefined && this.targetDoc.y !== undefined) {
+ const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms)));
const time = 10;
const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y);
const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x);
@@ -769,7 +833,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
await timer(0.1);
}
}
- }
+ };
//Command click
@action
@@ -784,13 +848,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.removeFromArray(this._dragArray, doc);
}
this.selectPres();
- }
+ };
removeFromArray = (arr: any[], val: any) => {
const index: number = arr.indexOf(val);
const ret: any[] = arr.splice(index, 1);
arr = ret;
- }
+ };
//Shift click
@action
@@ -805,28 +869,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
this.selectPres();
- }
+ };
//regular click
@action
- regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean) => {
+ regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true) => {
this._selectedArray.clear();
this._selectedArray.set(doc, undefined);
this._eleArray.splice(0, this._eleArray.length, ref);
this._dragArray.splice(0, this._dragArray.length, drag);
focus && this.selectElement(doc);
- this.selectPres();
- }
+ selectPres && this.selectPres();
+ };
modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean) => {
if (cmdClick) this.multiSelect(doc, ref, drag);
else if (shiftClick) this.shiftSelect(doc, ref, drag);
else this.regularSelect(doc, ref, drag, focus);
- }
+ };
static keyEventsWrapper = (e: KeyboardEvent) => {
PresBox.Instance.keyEvents(e);
- }
+ };
// Key for when the presentaiton is active
@action
@@ -834,57 +898,75 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (e.target instanceof HTMLInputElement) return;
let handled = false;
const anchorNode = document.activeElement as HTMLDivElement;
- if (anchorNode && anchorNode.className?.includes("lm_title")) return;
+ if (anchorNode && anchorNode.className?.includes('lm_title')) return;
switch (e.key) {
- case "Backspace":
- if (this.layoutDoc.presStatus === "edit") {
- undoBatch(action(() => {
- for (const doc of Array.from(this._selectedArray.keys())) {
- this.removeDocument(doc);
- }
- this._selectedArray.clear();
- this._eleArray.length = 0;
- this._dragArray.length = 0;
- }))();
+ case 'Backspace':
+ if (this.layoutDoc.presStatus === 'edit') {
+ undoBatch(
+ action(() => {
+ for (const doc of Array.from(this._selectedArray.keys())) {
+ this.removeDocument(doc);
+ }
+ this._selectedArray.clear();
+ this._eleArray.length = 0;
+ this._dragArray.length = 0;
+ })
+ )();
handled = true;
}
break;
- case "Escape":
- if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); }
- else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; }
- else this.layoutDoc.presStatus = "edit";
+ case 'Escape':
+ if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
+ this.updateMinimize();
+ } else if (this.layoutDoc.presStatus === 'edit') {
+ this._selectedArray.clear();
+ this._eleArray.length = this._dragArray.length = 0;
+ } else this.layoutDoc.presStatus = 'edit';
if (this._presTimer) clearTimeout(this._presTimer);
handled = true;
break;
- case "Down": case "ArrowDown":
- case "Right": case "ArrowRight":
- if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { // TODO: update to work properly
+ case 'Down':
+ case 'ArrowDown':
+ case 'Right':
+ case 'ArrowRight':
+ if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) {
+ // TODO: update to work properly
this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1;
this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined);
} else {
this.next();
- if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; }
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
}
handled = true;
break;
- case "Up": case "ArrowUp":
- case "Left": case "ArrowLeft":
- if (e.shiftKey && this.itemIndex !== 0) { // TODO: update to work properly
+ case 'Up':
+ case 'ArrowUp':
+ case 'Left':
+ case 'ArrowLeft':
+ if (e.shiftKey && this.itemIndex !== 0) {
+ // TODO: update to work properly
this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1;
this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined);
} else {
this.back();
- if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; }
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
}
handled = true;
break;
- case "Spacebar": case " ":
+ case 'Spacebar':
+ case ' ':
if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex);
else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer);
handled = true;
break;
- case "a":
- if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === "edit") {
+ case 'a':
+ if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === 'edit') {
this._selectedArray.clear();
this.childDocs.forEach(doc => this._selectedArray.set(doc, undefined));
handled = true;
@@ -896,10 +978,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.stopPropagation();
e.preventDefault();
}
- }
+ };
/**
- *
+ *
*/
@action
viewPaths = () => {
@@ -907,7 +989,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (srcContext) {
this.togglePath(srcContext);
}
- }
+ };
getAllIndexes = (arr: Doc[], val: Doc): number[] => {
const indexes = [];
@@ -915,7 +997,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
arr[i] === val && indexes.push(i);
}
return indexes;
- }
+ };
// Adds the index in the pres path graphically
@computed get order() {
@@ -923,104 +1005,109 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const docs: Doc[] = [];
const presCollection = Cast(this.rootDoc.presCollection, Doc, null);
const dv = DocumentManager.Instance.getDocumentView(presCollection);
- this.childDocs.filter(doc => Cast(doc.presentationTargetDoc, Doc, null)).forEach((doc, index) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
- const srcContext = Cast(tagDoc.context, Doc, null);
- const width = NumCast(tagDoc._width) / 10;
- const height = Math.max(NumCast(tagDoc._height) / 10, 15);
- const edge = Math.max(width, height);
- const fontSize = edge * 0.8;
- const gap = 2;
- if (presCollection === srcContext) {
- // Case A: Document is contained within the collection
- if (docs.includes(tagDoc)) {
- const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length;
- docs.push(tagDoc);
- order.push(
- <div className="pathOrder"
- key={tagDoc.id + 'pres' + index}
- style={{ top: NumCast(tagDoc.y) + (prevOccurances * (edge + gap) - (edge / 2)), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }}
- onClick={() => this.selectElement(doc)}>
- <div className="pathOrder-frame">{index + 1}</div>
- </div>);
- } else {
+ this.childDocs
+ .filter(doc => Cast(doc.presentationTargetDoc, Doc, null))
+ .forEach((doc, index) => {
+ const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const srcContext = Cast(tagDoc.context, Doc, null);
+ const width = NumCast(tagDoc._width) / 10;
+ const height = Math.max(NumCast(tagDoc._height) / 10, 15);
+ const edge = Math.max(width, height);
+ const fontSize = edge * 0.8;
+ const gap = 2;
+ if (presCollection === srcContext) {
+ // Case A: Document is contained within the collection
+ if (docs.includes(tagDoc)) {
+ const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length;
+ docs.push(tagDoc);
+ order.push(
+ <div
+ className="pathOrder"
+ key={tagDoc.id + 'pres' + index}
+ style={{ top: NumCast(tagDoc.y) + (prevOccurances * (edge + gap) - edge / 2), left: NumCast(tagDoc.x) - edge / 2, width: edge, height: edge, fontSize: fontSize }}
+ onClick={() => this.selectElement(doc)}>
+ <div className="pathOrder-frame">{index + 1}</div>
+ </div>
+ );
+ } else {
+ docs.push(tagDoc);
+ order.push(
+ <div
+ className="pathOrder"
+ key={tagDoc.id + 'pres' + index}
+ style={{ top: NumCast(tagDoc.y) - edge / 2, left: NumCast(tagDoc.x) - edge / 2, width: edge, height: edge, fontSize: fontSize }}
+ onClick={() => this.selectElement(doc)}>
+ <div className="pathOrder-frame">{index + 1}</div>
+ </div>
+ );
+ }
+ } else if (doc.presPinView && presCollection === tagDoc && dv) {
+ // Case B: Document is presPinView and is presCollection
+ const scale: number = 1 / NumCast(doc.presPinViewScale);
+ const height: number = dv.props.PanelHeight() * scale;
+ const width: number = dv.props.PanelWidth() * scale;
+ const indWidth = width / 10;
+ const indHeight = Math.max(height / 10, 15);
+ const indEdge = Math.max(indWidth, indHeight);
+ const indFontSize = indEdge * 0.8;
+ const xLoc: number = NumCast(doc.presPinViewX) - width / 2;
+ const yLoc: number = NumCast(doc.presPinViewY) - height / 2;
docs.push(tagDoc);
order.push(
- <div className="pathOrder"
- key={tagDoc.id + 'pres' + index}
- style={{ top: NumCast(tagDoc.y) - (edge / 2), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }}
- onClick={() => this.selectElement(doc)}>
- <div className="pathOrder-frame">{index + 1}</div>
- </div>);
+ <>
+ <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top: yLoc - indEdge / 2, left: xLoc - indEdge / 2, width: indEdge, height: indEdge, fontSize: indFontSize }} onClick={() => this.selectElement(doc)}>
+ <div className="pathOrder-frame">{index + 1}</div>
+ </div>
+ <div className="pathOrder-presPinView" style={{ top: yLoc, left: xLoc, width: width, height: height, borderWidth: indEdge / 10 }}></div>
+ </>
+ );
}
- } else if (doc.presPinView && presCollection === tagDoc && dv) {
- // Case B: Document is presPinView and is presCollection
- const scale: number = 1 / NumCast(doc.presPinViewScale);
- const height: number = dv.props.PanelHeight() * scale;
- const width: number = dv.props.PanelWidth() * scale;
- const indWidth = width / 10;
- const indHeight = Math.max(height / 10, 15);
- const indEdge = Math.max(indWidth, indHeight);
- const indFontSize = indEdge * 0.8;
- const xLoc: number = NumCast(doc.presPinViewX) - (width / 2);
- const yLoc: number = NumCast(doc.presPinViewY) - (height / 2);
- docs.push(tagDoc);
- order.push(
- <>
- <div className="pathOrder"
- key={tagDoc.id + 'pres' + index}
- style={{ top: yLoc - (indEdge / 2), left: xLoc - (indEdge / 2), width: indEdge, height: indEdge, fontSize: indFontSize }}
- onClick={() => this.selectElement(doc)}
- >
- <div className="pathOrder-frame">{index + 1}</div>
- </div>
- <div className="pathOrder-presPinView" style={{ top: yLoc, left: xLoc, width: width, height: height, borderWidth: indEdge / 10 }}></div>
- </>);
- }
- });
+ });
return order;
}
/**
* Method called for viewing paths which adds a single line with
* points at the center of each document added.
- * Design choice: When this is called it sets _fitToBox as true so the
+ * Design choice: When this is called it sets _fitContentsToBox as true so the
* user can have an overview of all of the documents in the collection.
* (Design needed for when documents in presentation trail are in another
* collection)
*/
@computed get paths() {
- let pathPoints = "";
+ let pathPoints = '';
const presCollection = Cast(this.rootDoc.presCollection, Doc, null);
this.childDocs.forEach((doc, index) => {
const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
const srcContext = Cast(tagDoc?.context, Doc, null);
if (tagDoc && presCollection === srcContext) {
- const n1x = NumCast(tagDoc.x) + (NumCast(tagDoc._width) / 2);
- const n1y = NumCast(tagDoc.y) + (NumCast(tagDoc._height) / 2);
- if (index = 0) pathPoints = n1x + "," + n1y;
- else pathPoints = pathPoints + " " + n1x + "," + n1y;
+ const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2;
+ const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2;
+ if ((index = 0)) pathPoints = n1x + ',' + n1y;
+ else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
} else if (doc.presPinView && presCollection === tagDoc) {
const n1x = NumCast(doc.presPinViewX);
const n1y = NumCast(doc.presPinViewY);
- if (index = 0) pathPoints = n1x + "," + n1y;
- else pathPoints = pathPoints + " " + n1x + "," + n1y;
+ if ((index = 0)) pathPoints = n1x + ',' + n1y;
+ else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
}
});
- return (<polyline
- points={pathPoints}
- style={{
- opacity: 1,
- stroke: "#69a6db",
- strokeWidth: 5,
- strokeDasharray: '10 5',
- boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
- }}
- fill="none"
- markerStart="url(#markerArrow)"
- markerMid="url(#markerSquare)"
- markerEnd="url(#markerSquareFilled)"
- />);
+ return (
+ <polyline
+ points={pathPoints}
+ style={{
+ opacity: 1,
+ stroke: '#69a6db',
+ strokeWidth: 5,
+ strokeDasharray: '10 5',
+ boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
+ }}
+ fill="none"
+ markerStart="url(#markerArrow)"
+ markerMid="url(#markerSquare)"
+ markerEnd="url(#markerSquareFilled)"
+ />
+ );
}
// Converts seconds to ms and updates presTransition
@@ -1029,8 +1116,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
if (timeInMS > 10000) timeInMS = 10000;
- Array.from(this._selectedArray.keys()).forEach((doc) => doc.presTransition = timeInMS);
- }
+ Array.from(this._selectedArray.keys()).forEach(doc => (doc.presTransition = timeInMS));
+ };
// Converts seconds to ms and updates presTransition
setZoom = (number: String, change?: number) => {
@@ -1038,8 +1125,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (change) scale += change;
if (scale < 0.01) scale = 0.01;
if (scale > 1.5) scale = 1.5;
- Array.from(this._selectedArray.keys()).forEach((doc) => doc.presZoom = scale);
- }
+ Array.from(this._selectedArray.keys()).forEach(doc => (doc.presZoom = scale));
+ };
// Converts seconds to ms and updates presDuration
setDurationTime = (number: String, change?: number) => {
@@ -1047,8 +1134,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
if (timeInMS > 20000) timeInMS = 20000;
- Array.from(this._selectedArray.keys()).forEach((doc) => doc.presDuration = timeInMS);
- }
+ Array.from(this._selectedArray.keys()).forEach(doc => (doc.presDuration = timeInMS));
+ };
/**
* When the movement dropdown is changes
@@ -1056,7 +1143,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
updateMovement = action((movement: any, all?: boolean) => {
const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
- array.forEach((doc) => {
+ array.forEach(doc => {
switch (movement) {
case PresMovement.Zoom: //Pan and zoom
doc.presMovement = PresMovement.Zoom;
@@ -1068,7 +1155,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
doc.presJump = true;
doc.presMovement = PresMovement.Jump;
break;
- case PresMovement.None: default:
+ case PresMovement.None:
+ default:
doc.presMovement = PresMovement.None;
break;
}
@@ -1079,31 +1167,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
updateHideBefore = (activeItem: Doc) => {
activeItem.presHideBefore = !activeItem.presHideBefore;
- Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideBefore = activeItem.presHideBefore);
- }
+ Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideBefore = activeItem.presHideBefore));
+ };
@undoBatch
@action
updateHideAfter = (activeItem: Doc) => {
activeItem.presHideAfter = !activeItem.presHideAfter;
- Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideAfter = activeItem.presHideAfter);
- }
+ Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideAfter = activeItem.presHideAfter));
+ };
@undoBatch
@action
updateOpenDoc = (activeItem: Doc) => {
activeItem.openDocument = !activeItem.openDocument;
- Array.from(this._selectedArray.keys()).forEach((doc) => {
+ Array.from(this._selectedArray.keys()).forEach(doc => {
doc.openDocument = activeItem.openDocument;
});
- }
+ };
@undoBatch
@action
updateEffectDirection = (effect: any, all?: boolean) => {
const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
- array.forEach((doc) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ array.forEach(doc => {
+ const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null);
switch (effect) {
case PresEffect.Left:
tagDoc.presEffectDirection = PresEffect.Left;
@@ -1117,19 +1205,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
case PresEffect.Bottom:
tagDoc.presEffectDirection = PresEffect.Bottom;
break;
- case PresEffect.Center: default:
+ case PresEffect.Center:
+ default:
tagDoc.presEffectDirection = PresEffect.Center;
break;
}
});
- }
+ };
@undoBatch
@action
updateEffect = (effect: any, all?: boolean) => {
const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
- array.forEach((doc) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ array.forEach(doc => {
+ const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null);
switch (effect) {
case PresEffect.Bounce:
tagDoc.presEffect = PresEffect.Bounce;
@@ -1146,19 +1235,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
case PresEffect.Rotate:
tagDoc.presEffect = PresEffect.Rotate;
break;
- case PresEffect.None: default:
+ case PresEffect.None:
+ default:
tagDoc.presEffect = PresEffect.None;
break;
}
});
- }
+ };
_batch: UndoManager.Batch | undefined = undefined;
@computed get transitionDropdown() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection);
+ const isPresCollection: boolean = targetDoc === this.layoutDoc.presCollection;
const isPinWithView: boolean = BoolCast(activeItem.presPinView);
if (activeItem && targetDoc) {
const type = targetDoc.type;
@@ -1166,168 +1256,326 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75;
let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2;
if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
- const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None';
+ const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None';
activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom';
return (
- <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}>
+ <div
+ className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`}
+ onPointerDown={e => e.stopPropagation()}
+ onPointerUp={e => e.stopPropagation()}
+ onClick={action(e => {
+ e.stopPropagation();
+ this.openMovementDropdown = false;
+ this.openEffectDropdown = false;
+ })}>
<div className="ribbon-box">
Movement
- {isPresCollection || (isPresCollection && isPinWithView) ?
+ {isPresCollection || (isPresCollection && isPinWithView) ? (
<div className="ribbon-property" style={{ marginLeft: 0, height: 25, textAlign: 'left', paddingLeft: 5, paddingRight: 5, fontSize: 10 }}>
- {this.scrollable ? "Scroll to pinned view" : !isPinWithView ? "No movement" : "Pan & Zoom to pinned view"}
+ {this.scrollable ? 'Scroll to pinned view' : !isPinWithView ? 'No movement' : 'Pan & Zoom to pinned view'}
</div>
- :
- <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+ ) : (
+ <div
+ className="presBox-dropdown"
+ onClick={action(e => {
+ e.stopPropagation();
+ this.openMovementDropdown = !this.openMovementDropdown;
+ })}
+ style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
{this.setMovementName(activeItem.presMovement, activeItem)}
- <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
- <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None</div>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom</div>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>Pan</div>
- <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>Jump cut</div>
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>
+ None
+ </div>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>
+ Pan {'&'} Zoom
+ </div>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>
+ Pan
+ </div>
+ <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>
+ Jump cut
+ </div>
</div>
</div>
- }
- <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}>
+ )}
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Zoom (% screen filled)</div>
<div className="ribbon-property">
- <input className="presBox-input"
- type="number" value={zoom}
- onChange={action((e) => this.setZoom(e.target.value))} />%
+ <input className="presBox-input" type="number" value={zoom} onChange={action(e => this.setZoom(e.target.value))} />%
</div>
<div className="ribbon-propertyUpDown">
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
- <FontAwesomeIcon icon={"caret-up"} />
+ <FontAwesomeIcon icon={'caret-up'} />
</div>
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
- <FontAwesomeIcon icon={"caret-down"} />
+ <FontAwesomeIcon icon={'caret-down'} />
</div>
</div>
</div>
- <input type="range" step="1" min="0" max="150" value={zoom}
- className={`toolbar-slider ${activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}
+ <input
+ type="range"
+ step="1"
+ min="0"
+ max="150"
+ value={zoom}
+ className={`toolbar-slider ${activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}
id="toolbar-slider"
- onPointerDown={() => this._batch = UndoManager.StartBatch("presZoom")}
+ onPointerDown={() => (this._batch = UndoManager.StartBatch('presZoom'))}
onPointerUp={() => this._batch?.end()}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
this.setZoom(e.target.value);
- }} />
- <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}>
+ }}
+ />
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Movement Speed</div>
<div className="ribbon-property">
- <input className="presBox-input"
- type="number" value={transitionSpeed}
- onChange={action((e) => this.setTransitionTime(e.target.value))} /> s
+ <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setTransitionTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), 1000))}>
- <FontAwesomeIcon icon={"caret-up"} />
+ <FontAwesomeIcon icon={'caret-up'} />
</div>
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), -1000))}>
- <FontAwesomeIcon icon={"caret-down"} />
+ <FontAwesomeIcon icon={'caret-down'} />
</div>
</div>
</div>
- <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed}
- className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}
+ <input
+ type="range"
+ step="0.1"
+ min="0.1"
+ max="10"
+ value={transitionSpeed}
+ className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}
id="toolbar-slider"
- onPointerDown={() => this._batch = UndoManager.StartBatch("presTransition")}
+ onPointerDown={() => (this._batch = UndoManager.StartBatch('presTransition'))}
onPointerUp={() => this._batch?.end()}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
this.setTransitionTime(e.target.value);
- }} />
- <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}>
+ }}
+ />
+ <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}>
<div className="slider-text">Fast</div>
<div className="slider-text">Medium</div>
<div className="slider-text">Slow</div>
</div>
</div>
<div className="ribbon-box">
- Visibility {"&"} Duration
+ Visibility {'&'} Duration
<div className="ribbon-doubleButton">
- {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideBefore ? "active" : ""}`} onClick={() => this.updateHideBefore(activeItem)}>Hide before</div></Tooltip>}
- {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>}
- <Tooltip title={<><div className="dash-tooltip">{"Open in lightbox view"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Lightbox</div></Tooltip>
+ {isPresCollection ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Hide before presented'}</div>
+ </>
+ }>
+ <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}>
+ Hide before
+ </div>
+ </Tooltip>
+ )}
+ {isPresCollection ? null : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Hide after presented'}</div>
+ </>
+ }>
+ <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}>
+ Hide after
+ </div>
+ </Tooltip>
+ )}
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Open in lightbox view'}</div>
+ </>
+ }>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}>
+ Lightbox
+ </div>
+ </Tooltip>
</div>
- {(type === DocumentType.AUDIO || type === DocumentType.VID) ? (null) : <>
- <div className="ribbon-doubleButton" >
- <div className="presBox-subheading">Slide Duration</div>
- <div className="ribbon-property">
- <input className="presBox-input"
- type="number" value={duration}
- onChange={action((e) => this.setDurationTime(e.target.value))} /> s
+ {type === DocumentType.AUDIO || type === DocumentType.VID ? null : (
+ <>
+ <div className="ribbon-doubleButton">
+ <div className="presBox-subheading">Slide Duration</div>
+ <div className="ribbon-property">
+ <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setDurationTime(e.target.value))} /> s
+ </div>
+ <div className="ribbon-propertyUpDown">
+ <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}>
+ <FontAwesomeIcon icon={'caret-up'} />
+ </div>
+ <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}>
+ <FontAwesomeIcon icon={'caret-down'} />
+ </div>
+ </div>
</div>
- <div className="ribbon-propertyUpDown">
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}>
- <FontAwesomeIcon icon={"caret-up"} />
+ <input
+ type="range"
+ step="0.1"
+ min="0.1"
+ max="20"
+ value={duration}
+ style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'block' }}
+ className={'toolbar-slider'}
+ id="duration-slider"
+ onPointerDown={() => {
+ this._batch = UndoManager.StartBatch('presDuration');
+ }}
+ onPointerUp={() => {
+ if (this._batch) this._batch.end();
+ }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ e.stopPropagation();
+ this.setDurationTime(e.target.value);
+ }}
+ />
+ <div className={'slider-headers'} style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'grid' }}>
+ <div className="slider-text">Short</div>
+ <div className="slider-text">Medium</div>
+ <div className="slider-text">Long</div>
+ </div>
+ </>
+ )}
+ </div>
+ {isPresCollection ? null : (
+ <div className="ribbon-box">
+ Effects
+ <div
+ className="presBox-dropdown"
+ onClick={action(e => {
+ e.stopPropagation();
+ this.openEffectDropdown = !this.openEffectDropdown;
+ })}
+ style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+ {effect.toString()}
+ <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+ <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
+ <div
+ className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.None || !this.activeItem.presEffect ? 'active' : ''}`}
+ onPointerDown={e => e.stopPropagation()}
+ onClick={() => this.updateEffect(PresEffect.None)}>
+ None
+ </div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Fade ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>
+ Fade In
+ </div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Flip ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>
+ Flip
+ </div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Rotate ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>
+ Rotate
+ </div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Bounce ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>
+ Bounce
</div>
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}>
- <FontAwesomeIcon icon={"caret-down"} />
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Roll ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>
+ Roll
</div>
</div>
</div>
- <input type="range" step="0.1" min="0.1" max="20" value={duration}
- style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }}
- className={"toolbar-slider"} id="duration-slider"
- onPointerDown={() => { this._batch = UndoManager.StartBatch("presDuration"); }}
- onPointerUp={() => { if (this._batch) this._batch.end(); }}
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }}
- />
- <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}>
- <div className="slider-text">Short</div>
- <div className="slider-text">Medium</div>
- <div className="slider-text">Long</div>
- </div>
- </>}
- </div>
- {isPresCollection ? (null) : <div className="ribbon-box">
- Effects
- <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
- {effect}
- <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
- <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.None || !targetDoc.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div>
+ <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? 'none' : 'inline-flex' }}>
+ <div className="presBox-subheading">Effect direction</div>
+ <div className="ribbon-property">{this.effectDirection}</div>
</div>
- </div>
- <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}>
- <div className="presBox-subheading" >Effect direction</div>
- <div className="ribbon-property">
- {this.effectDirection}
+ <div className="effectDirection" style={{ display: effect === 'None' ? 'none' : 'grid', width: 40 }}>
+ <Tooltip title={<div className="dash-tooltip">{'Enter from left'}</div>}>
+ <div
+ style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
+ onClick={() => this.updateEffectDirection(PresEffect.Left)}>
+ <FontAwesomeIcon icon={'angle-right'} />
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">{'Enter from right'}</div>}>
+ <div
+ style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
+ onClick={() => this.updateEffectDirection(PresEffect.Right)}>
+ <FontAwesomeIcon icon={'angle-left'} />
+ </div>
+ </Tooltip>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Enter from top'}</div>
+ </>
+ }>
+ <div
+ style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
+ onClick={() => this.updateEffectDirection(PresEffect.Top)}>
+ <FontAwesomeIcon icon={'angle-down'} />
+ </div>
+ </Tooltip>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Enter from bottom'}</div>
+ </>
+ }>
+ <div
+ style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }}
+ onClick={() => this.updateEffectDirection(PresEffect.Bottom)}>
+ <FontAwesomeIcon icon={'angle-up'} />
+ </div>
+ </Tooltip>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Enter from center'}</div>
+ </>
+ }>
+ <div
+ style={{
+ gridColumn: 2,
+ gridRow: 2,
+ width: 10,
+ height: 10,
+ alignSelf: 'center',
+ justifySelf: 'center',
+ border: this.activeItem.presEffectDirection === PresEffect.Center || !this.activeItem.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : 'solid 2px black',
+ borderRadius: '100%',
+ cursor: 'pointer',
+ }}
+ onClick={() => this.updateEffectDirection(PresEffect.Center)}></div>
+ </Tooltip>
</div>
</div>
- <div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
- </div>
- </div>}
+ )}
<div className="ribbon-final-box">
<div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}>
Apply to all
</div>
</div>
- </div >
+ </div>
);
}
}
@computed get effectDirection(): string {
let effect = '';
- switch (this.targetDoc.presEffectDirection) {
- case 'left': effect = "Enter from left"; break;
- case 'right': effect = "Enter from right"; break;
- case 'top': effect = "Enter from top"; break;
- case 'bottom': effect = "Enter from bottom"; break;
- default: effect = "Enter from center"; break;
+ switch (this.activeItem.presEffectDirection) {
+ case 'left':
+ effect = 'Enter from left';
+ break;
+ case 'right':
+ effect = 'Enter from right';
+ break;
+ case 'top':
+ effect = 'Enter from top';
+ break;
+ case 'bottom':
+ effect = 'Enter from bottom';
+ break;
+ default:
+ effect = 'Enter from center';
+ break;
}
return effect;
}
@@ -1338,9 +1586,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
this.updateMovement(activeItem.presMovement, true);
- this.updateEffect(targetDoc.presEffect, true);
- this.updateEffectDirection(targetDoc.presEffectDirection, true);
- array.forEach((doc) => {
+ this.updateEffect(activeItem.presEffect, true);
+ this.updateEffectDirection(activeItem.presEffectDirection, true);
+ array.forEach(doc => {
const curDoc = Cast(doc, Doc, null);
const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
if (tagDoc && targetDoc) {
@@ -1350,61 +1598,86 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
curDoc.presHideAfter = activeItem.presHideAfter;
}
});
- }
+ };
@computed get presPinViewOptionsDropdown() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 16, filter: 'invert(1)' }} />;
+ const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 16, filter: 'invert(1)' }} />;
return (
<>
- {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)}
+ {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null}
<div className="ribbon-doubleButton">
- <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : "" }}
- onClick={() => {
- activeItem.presPinView = !activeItem.presPinView;
- targetDoc.presPinView = activeItem.presPinView;
- if (activeItem.presPinView) {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinView = true;
- activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
- } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinView = true;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const width = targetDoc._clipWidth;
- activeItem.presPinClipWidth = width;
- activeItem.presPinView = true;
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}</div>
+ </>
+ }>
+ <div
+ className="ribbon-toggle"
+ style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : '' }}
+ onClick={() => {
+ activeItem.presPinView = !activeItem.presPinView;
+ targetDoc.presPinView = activeItem.presPinView;
+ if (activeItem.presPinView) {
+ if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) {
+ const scroll = targetDoc._scrollTop;
+ activeItem.presPinView = true;
+ activeItem.presPinViewScroll = scroll;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
+ } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) {
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeItem.presPinView = true;
+ activeItem.presPinViewX = x;
+ activeItem.presPinViewY = y;
+ activeItem.presPinViewScale = scale;
+ } else if (targetDoc.type === DocumentType.COMPARISON) {
+ const width = targetDoc._clipWidth;
+ activeItem.presPinClipWidth = width;
+ activeItem.presPinView = true;
+ }
}
- }
- }}>{presPinWithViewIcon}</div></Tooltip>
- {activeItem.presPinView ? <Tooltip title={<><div className="dash-tooltip">{"Update the pinned view with the view of the selected document"}</div></>}><div className="ribbon-button"
- onClick={() => {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const clipWidth = targetDoc._clipWidth;
- activeItem.presPinClipWidth = clipWidth;
- } else {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
- }
- }}>Update</div></Tooltip> : (null)}
+ }}>
+ {presPinWithViewIcon}
+ </div>
+ </Tooltip>
+ {activeItem.presPinView ? (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Update the pinned view with the view of the selected document'}</div>
+ </>
+ }>
+ <div
+ className="ribbon-button"
+ onClick={() => {
+ if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
+ const scroll = targetDoc._scrollTop;
+ activeItem.presPinViewScroll = scroll;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1;
+ } else if (targetDoc.type === DocumentType.COMPARISON) {
+ const clipWidth = targetDoc._clipWidth;
+ activeItem.presPinClipWidth = clipWidth;
+ } else {
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeItem.presPinViewX = x;
+ activeItem.presPinViewY = y;
+ activeItem.presPinViewScale = scale;
+ }
+ }}>
+ Update
+ </div>
+ </Tooltip>
+ ) : null}
</div>
</>
);
@@ -1415,35 +1688,58 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetDoc: Doc = this.targetDoc;
return (
<>
- {this.panable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Pan X</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number" value={NumCast(activeItem.presPinViewX)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} />
+ {this.panable ? (
+ <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
+ <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
+ <div className="presBox-subheading">Pan X</div>
+ <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
+ <input
+ className="presBox-input"
+ style={{ textAlign: 'left', width: 50 }}
+ type="number"
+ value={NumCast(activeItem.presPinViewX)}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ const val = e.target.value;
+ activeItem.presPinViewX = Number(val);
+ })}
+ />
+ </div>
</div>
- </div>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Pan Y</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number" value={NumCast(activeItem.presPinViewY)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} />
+ <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
+ <div className="presBox-subheading">Pan Y</div>
+ <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
+ <input
+ className="presBox-input"
+ style={{ textAlign: 'left', width: 50 }}
+ type="number"
+ value={NumCast(activeItem.presPinViewY)}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ const val = e.target.value;
+ activeItem.presPinViewY = Number(val);
+ })}
+ />
+ </div>
</div>
- </div>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Scale</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number" value={NumCast(activeItem.presPinViewScale)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} />
+ <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
+ <div className="presBox-subheading">Scale</div>
+ <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
+ <input
+ className="presBox-input"
+ style={{ textAlign: 'left', width: 50 }}
+ type="number"
+ value={NumCast(activeItem.presPinViewScale)}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ const val = e.target.value;
+ activeItem.presPinViewScale = Number(val);
+ })}
+ />
+ </div>
</div>
</div>
- </div> : (null)}
+ ) : null}
</>
);
}
@@ -1453,17 +1749,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetDoc: Doc = this.targetDoc;
return (
<>
- {this.scrollable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}>
- <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
- <div className="presBox-subheading">Scroll</div>
- <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
- <input className="presBox-input"
- style={{ textAlign: 'left', width: 50 }}
- type="number" value={NumCast(activeItem.presPinViewScroll)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} />
+ {this.scrollable ? (
+ <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
+ <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
+ <div className="presBox-subheading">Scroll</div>
+ <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
+ <input
+ className="presBox-input"
+ style={{ textAlign: 'left', width: 50 }}
+ type="number"
+ value={NumCast(activeItem.presPinViewScroll)}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ const val = e.target.value;
+ activeItem.presPinViewScroll = Number(val);
+ })}
+ />
+ </div>
</div>
</div>
- </div> : (null)}
+ ) : null}
</>
);
}
@@ -1473,38 +1778,45 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const list = this.childDocs.map((doc, i) => {
if (i > this.itemIndex) {
return (
- <option>{i + 1}. {doc.title}</option>
+ <option>
+ {i + 1}. {StrCast(doc.title)}
+ </option>
);
}
});
- return (
- list
- );
+ return list;
}
@computed get mediaOptionsDropdown() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
+ const clipStart: number = NumCast(activeItem.clipStart);
+ const clipEnd: number = NumCast(activeItem.clipEnd);
const duration = Math.round(NumCast(activeItem[`${Doc.LayoutFieldKey(activeItem)}-duration`]) * 10);
const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc);
- const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + ". " + this.childDocs[mediaStopDocInd - 1].title : "";
+ const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + '. ' + this.childDocs[mediaStopDocInd - 1].title : '';
if (activeItem && targetDoc) {
return (
<div>
<div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div>
<div className="ribbon-box">
- Start {"&"} End Time
- <div className={"slider-headers"}>
- <div className="slider-block" >
+ Start {'&'} End Time
+ <div className={'slider-headers'}>
+ <div className="slider-block">
<div className="slider-text" style={{ fontWeight: 500 }}>
Start time (s)
</div>
- <div id={"startTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
- <input className="presBox-input"
+ <div id={'startTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
+ <input
+ className="presBox-input"
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
- type="number" value={NumCast(activeItem.presStartTime)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })}
+ type="number"
+ value={NumCast(activeItem.presStartTime)}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ activeItem.presStartTime = Number(e.target.value);
+ })}
/>
</div>
</div>
@@ -1520,23 +1832,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="slider-text" style={{ fontWeight: 500 }}>
End time (s)
</div>
- <div id={"endTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
- <input className="presBox-input"
+ <div id={'endTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
+ <input
+ className="presBox-input"
+ onKeyDown={e => e.stopPropagation()}
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
- type="number" value={NumCast(activeItem.presEndTime)}
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presEndTime = Number(e.target.value); })}
+ type="number"
+ value={NumCast(activeItem.presEndTime)}
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ activeItem.presEndTime = Number(e.target.value);
+ })}
/>
</div>
</div>
</div>
<div className="multiThumb-slider">
- <input type="range" step="0.1" min="0" max={duration / 10} value={NumCast(activeItem.presEndTime)}
+ <input
+ type="range"
+ step="0.1"
+ min={clipStart}
+ max={clipEnd}
+ value={NumCast(activeItem.presEndTime)}
style={{ gridColumn: 1, gridRow: 1 }}
- className={`toolbar-slider ${"end"}`}
+ className={`toolbar-slider ${'end'}`}
id="toolbar-slider"
onPointerDown={() => {
- this._batch = UndoManager.StartBatch("presEndTime");
- const endBlock = document.getElementById("endTime");
+ this._batch = UndoManager.StartBatch('presEndTime');
+ const endBlock = document.getElementById('endTime');
if (endBlock) {
endBlock.style.color = Colors.LIGHT_GRAY;
endBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
@@ -1544,7 +1866,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}}
onPointerUp={() => {
this._batch?.end();
- const endBlock = document.getElementById("endTime");
+ const endBlock = document.getElementById('endTime');
if (endBlock) {
endBlock.style.color = Colors.BLACK;
endBlock.style.backgroundColor = Colors.LIGHT_GRAY;
@@ -1553,14 +1875,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
activeItem.presEndTime = Number(e.target.value);
- }} />
- <input type="range" step="0.1" min="0" max={duration / 10} value={NumCast(activeItem.presStartTime)}
+ }}
+ />
+ <input
+ type="range"
+ step="0.1"
+ min={clipStart}
+ max={clipEnd}
+ value={NumCast(activeItem.presStartTime)}
style={{ gridColumn: 1, gridRow: 1 }}
- className={`toolbar-slider ${"start"}`}
+ className={`toolbar-slider ${'start'}`}
id="toolbar-slider"
onPointerDown={() => {
- this._batch = UndoManager.StartBatch("presStartTime");
- const startBlock = document.getElementById("startTime");
+ this._batch = UndoManager.StartBatch('presStartTime');
+ const startBlock = document.getElementById('startTime');
if (startBlock) {
startBlock.style.color = Colors.LIGHT_GRAY;
startBlock.style.backgroundColor = Colors.MEDIUM_BLUE;
@@ -1568,7 +1896,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}}
onPointerUp={() => {
this._batch?.end();
- const startBlock = document.getElementById("startTime");
+ const startBlock = document.getElementById('startTime');
if (startBlock) {
startBlock.style.color = Colors.BLACK;
startBlock.style.backgroundColor = Colors.LIGHT_GRAY;
@@ -1577,12 +1905,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
activeItem.presStartTime = Number(e.target.value);
- }} />
+ }}
+ />
</div>
- <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}>
- <div className="slider-text">0 s</div>
+ <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}>
+ <div className="slider-text">{clipStart} s</div>
<div className="slider-text"></div>
- <div className="slider-text">{duration / 10} s</div>
+ <div className="slider-text">{clipEnd} s</div>
</div>
</div>
<div className="ribbon-final-box">
@@ -1590,38 +1919,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presBox-subheading">Start playing:</div>
<div className="presBox-radioButtons">
<div className="checkbox-container">
- <input className="presBox-checkbox"
- type="checkbox"
- onChange={() => activeItem.mediaStart = "manual"}
- checked={activeItem.mediaStart === "manual"}
- />
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} />
<div>On click</div>
</div>
<div className="checkbox-container">
- <input className="presBox-checkbox"
- type="checkbox"
- onChange={() => activeItem.mediaStart = "auto"}
- checked={activeItem.mediaStart === "auto"}
- />
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} />
<div>Automatically</div>
</div>
</div>
<div className="presBox-subheading">Stop playing:</div>
<div className="presBox-radioButtons">
<div className="checkbox-container">
- <input className="presBox-checkbox"
- type="checkbox"
- onChange={() => activeItem.mediaStop = "manual"}
- checked={activeItem.mediaStop === "manual"}
- />
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} />
<div>At audio end time</div>
</div>
<div className="checkbox-container">
- <input className="presBox-checkbox"
- type="checkbox"
- onChange={() => activeItem.mediaStop = "auto"}
- checked={activeItem.mediaStop === "auto"}
- />
+ <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} />
<div>On slide change</div>
</div>
{/* <div className="checkbox-container">
@@ -1645,7 +1958,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
</div>
- </div >
+ </div>
);
}
}
@@ -1653,84 +1966,137 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get newDocumentToolbarDropdown() {
return (
<div>
- <div className={'presBox-toolbar-dropdown'} style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div
+ className={'presBox-toolbar-dropdown'}
+ style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }}
+ onClick={e => e.stopPropagation()}
+ onPointerUp={e => e.stopPropagation()}
+ onPointerDown={e => e.stopPropagation()}>
<div className="layout-container" style={{ height: 'max-content' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'blank';
+ this.createNewSlide(this.layout);
+ })}
+ />
+ <div
+ className="layout"
+ style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'title';
+ this.createNewSlide(this.layout);
+ })}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}>
- <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'header';
+ this.createNewSlide(this.layout);
+ })}>
+ <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
+ Section header
+ </div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}>
- <div className="title" style={{ alignSelf: 'center' }}>Title</div>
+ <div
+ className="layout"
+ style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+ onClick={action(() => {
+ this.layout = 'content';
+ this.createNewSlide(this.layout);
+ })}>
+ <div className="title" style={{ alignSelf: 'center' }}>
+ Title
+ </div>
<div className="content">Text goes here</div>
</div>
</div>
</div>
- </div >
+ </div>
);
}
@observable openLayouts: boolean = false;
@observable addFreeform: boolean = true;
- @observable layout: string = "";
- @observable title: string = "";
+ @observable layout: string = '';
+ @observable title: string = '';
@computed get newDocumentDropdown() {
return (
<div>
- <div className={"presBox-ribbon"} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
<div className="ribbon-box">
Slide Title: <br></br>
- <input className="ribbon-textInput" placeholder="..." type="text" name="fname"
- onChange={(e) => {
+ <input
+ className="ribbon-textInput"
+ placeholder="..."
+ type="text"
+ name="fname"
+ onChange={e => {
e.stopPropagation();
e.preventDefault();
- runInAction(() => this.title = e.target.value);
- }}>
- </input>
+ runInAction(() => (this.title = e.target.value));
+ }}></input>
</div>
<div className="ribbon-box">
Choose type:
<div className="ribbon-doubleButton">
- <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : Colors.LIGHT_BLUE }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div>
- <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div>
+ <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+ Text
+ </div>
+ <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+ Freeform
+ </div>
</div>
</div>
- <div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}>
+ <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}>
Preset layouts:
<div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
- <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'blank')} />
- <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'title')}>
+ <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} />
+ <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}>
<div className="title">Title</div>
<div className="subtitle">Subtitle</div>
</div>
- <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'header')}>
- <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div>
+ <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}>
+ <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
+ Section header
+ </div>
</div>
- <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'content')}>
- <div className="title" style={{ alignSelf: 'center' }}>Title</div>
+ <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}>
+ <div className="title" style={{ alignSelf: 'center' }}>
+ Title
+ </div>
<div className="content">Text goes here</div>
</div>
- <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'twoColumns')}>
- <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div>
- <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div>
- <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div>
+ <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}>
+ <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>
+ Title
+ </div>
+ <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>
+ Column one text
+ </div>
+ <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>
+ Column two text
+ </div>
</div>
</div>
- <div className="open-layout" onClick={action(() => this.openLayouts = !this.openLayouts)}>
- <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"caret-down"} size={"lg"} />
+ <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}>
+ <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} />
</div>
</div>
<div className="ribbon-final-box">
- <div className={this.title !== "" && (this.addFreeform && this.layout !== "" || !this.addFreeform) ? "ribbon-final-button-hidden" : "ribbon-final-button"} onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}>
+ <div
+ className={this.title !== '' && ((this.addFreeform && this.layout !== '') || !this.addFreeform) ? 'ribbon-final-button-hidden' : 'ribbon-final-button'}
+ onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}>
Create New Slide
</div>
</div>
</div>
- </div >
+ </div>
);
}
@@ -1738,7 +2104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
let doc = undefined;
if (layout) doc = this.createTemplate(layout);
if (freeform && layout) doc = this.createTemplate(layout, title);
- if (!freeform && !layout) doc = Docs.Create.TextDocument("", { _nativeWidth: 400, _width: 225, title: title });
+ if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title });
if (doc) {
const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
const data = Cast(presCollection?.data, listSpec(Doc));
@@ -1748,10 +2114,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
TabDocView.PinDoc(doc);
this.gotoDocument(this.childDocs.length, this.activeItem);
} else {
- this.props.addDocTab(doc, "add:right");
+ this.props.addDocTab(doc, 'add:right');
}
}
- }
+ };
createTemplate = (layout: string, input?: string) => {
const activeItem: Doc = this.activeItem;
@@ -1763,43 +2129,59 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20;
}
let doc = undefined;
- const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24pt", });
- const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" });
- const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" });
- const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" });
- const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" });
- const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" });
- const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" });
+ const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' });
+ const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' });
+ const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' });
+ const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' });
+ const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' });
+ const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' });
+ const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' });
switch (layout) {
case 'blank':
- doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y });
+ doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y });
break;
case 'title':
- doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
break;
case 'header':
- doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
break;
case 'content':
- doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
break;
case 'twoColumns':
- doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitToBox: true, x: x, y: y });
+ doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
break;
default:
break;
}
return doc;
- }
+ };
// Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
@computed get presentDropdown() {
return (
- <div className={`dropdown-play ${this.presentTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
- <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.updateMinimize(); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }))}>
+ <div className={`dropdown-play ${this.presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div
+ className="dropdown-play-button"
+ onClick={undoBatch(
+ action(() => {
+ this.updateMinimize();
+ this.turnOffEdit(true);
+ this.gotoDocument(this.itemIndex, this.activeItem);
+ })
+ )}>
Mini-player
</div>
- <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }))}>
+ <div
+ className="dropdown-play-button"
+ onClick={undoBatch(
+ action(() => {
+ this.layoutDoc.presStatus = 'manual';
+ this.turnOffEdit(true);
+ this.gotoDocument(this.itemIndex, this.activeItem);
+ })
+ )}>
Sidebar player
</div>
</div>
@@ -1810,7 +2192,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => {
const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
- const currentFrame = Cast(tagDoc._currentFrame, "number", null);
+ const currentFrame = Cast(tagDoc._currentFrame, 'number', null);
if (currentFrame === undefined) {
tagDoc._currentFrame = 0;
// CollectionFreeFormDocumentView.setupScroll(tagDoc, 0);
@@ -1820,19 +2202,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc);
tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1);
tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame));
- }
+ };
@action
prevKeyframe = (tagDoc: Doc, actItem: Doc): void => {
const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
- const currentFrame = Cast(tagDoc._currentFrame, "number", null);
+ const currentFrame = Cast(tagDoc._currentFrame, 'number', null);
if (currentFrame === undefined) {
tagDoc._currentFrame = 0;
// CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
}
CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice());
tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1);
- }
+ };
/**
* Returns the collection type as a string for headers
@@ -1843,15 +2225,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
let type: string = '';
if (activeItem) {
switch (targetDoc.type) {
- case DocumentType.PDF: type = "PDF"; break;
- case DocumentType.RTF: type = "Text node"; break;
- case DocumentType.COL: type = "Collection"; break;
- case DocumentType.AUDIO: type = "Audio"; break;
- case DocumentType.VID: type = "Video"; break;
- case DocumentType.IMG: type = "Image"; break;
- case DocumentType.WEB: type = "Web page"; break;
- case DocumentType.MAP: type = "Map"; break;
- default: type = "Other node"; break;
+ case DocumentType.PDF:
+ type = 'PDF';
+ break;
+ case DocumentType.RTF:
+ type = 'Text node';
+ break;
+ case DocumentType.COL:
+ type = 'Collection';
+ break;
+ case DocumentType.AUDIO:
+ type = 'Audio';
+ break;
+ case DocumentType.VID:
+ type = 'Video';
+ break;
+ case DocumentType.IMG:
+ type = 'Image';
+ break;
+ case DocumentType.WEB:
+ type = 'Web page';
+ break;
+ case DocumentType.MAP:
+ type = 'Map';
+ break;
+ default:
+ type = 'Other node';
+ break;
}
}
return type;
@@ -1860,66 +2260,122 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable private openActiveColorPicker: boolean = false;
@observable private openViewedColorPicker: boolean = false;
-
-
@computed get progressivizeDropdown() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
if (activeItem && targetDoc) {
- const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black";
- const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black";
+ const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black';
+ const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black';
return (
<div>
- <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+ <div
+ className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === 'edit' ? 'active' : ''}`}
+ onClick={e => e.stopPropagation()}
+ onPointerUp={e => e.stopPropagation()}
+ onPointerDown={e => e.stopPropagation()}>
<div className="ribbon-box">
{this.stringType} selected
- <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeChild}>Contents</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editProgressivize}>Edit</div>
+ <div
+ className="ribbon-doubleButton"
+ style={{
+ borderTop: 'solid 1px darkgrey',
+ display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none',
+ }}>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeChild}>
+ Contents
+ </div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editProgressivize}>
+ Edit
+ </div>
</div>
- <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}>
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Active text color</div>
- <div className="ribbon-colorBox" style={{ backgroundColor: activeFontColor, height: 15, width: 15 }} onClick={action(() => { this.openActiveColorPicker = !this.openActiveColorPicker; })}>
- </div>
+ <div
+ className="ribbon-colorBox"
+ style={{ backgroundColor: activeFontColor, height: 15, width: 15 }}
+ onClick={action(() => {
+ this.openActiveColorPicker = !this.openActiveColorPicker;
+ })}></div>
</div>
{this.activeColorPicker}
- <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}>
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Viewed font color</div>
- <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => this.openViewedColorPicker = !this.openViewedColorPicker)}>
- </div>
+ <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => (this.openViewedColorPicker = !this.openViewedColorPicker))}></div>
</div>
{this.viewedColorPicker}
- <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeZoom}>Zoom</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editZoomProgressivize}>Edit</div>
+ <div
+ className="ribbon-doubleButton"
+ style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? 'inline-flex' : 'none' }}>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeZoom}>
+ Zoom
+ </div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editZoomProgressivize}>
+ Edit
+ </div>
</div>
- <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}>
- <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeScroll}>Scroll</div>
- <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editScrollProgressivize}>Edit</div>
+ <div
+ className="ribbon-doubleButton"
+ style={{
+ borderTop: 'solid 1px darkgrey',
+ display: targetDoc._viewType === 'stacking' || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none',
+ }}>
+ <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeScroll}>
+ Scroll
+ </div>
+ <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editScrollProgressivize}>
+ Edit
+ </div>
</div>
</div>
<div className="ribbon-final-box">
Frames
<div className="ribbon-doubleButton">
<div className="ribbon-frameSelector">
- <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}>
- <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ <div
+ key="back"
+ title="back frame"
+ className="backKeyframe"
+ onClick={e => {
+ e.stopPropagation();
+ this.prevKeyframe(targetDoc, activeItem);
+ }}>
+ <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
</div>
- <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.keyFrameEditing ? "white" : "black", backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }}
- onClick={action(() => targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing)} >
+ <div
+ key="num"
+ title="toggle view all"
+ className="numKeyframe"
+ style={{ color: targetDoc.keyFrameEditing ? 'white' : 'black', backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }}
+ onClick={action(() => (targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}>
{NumCast(targetDoc._currentFrame)}
</div>
- <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}>
- <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ <div
+ key="fwd"
+ title="forward frame"
+ className="fwdKeyframe"
+ onClick={e => {
+ e.stopPropagation();
+ this.nextKeyframe(targetDoc, activeItem);
+ }}>
+ <FontAwesomeIcon icon={'caret-right'} size={'lg'} />
</div>
</div>
- <Tooltip title={<><div className="dash-tooltip">{"Last frame"}</div></>}><div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div></Tooltip>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Last frame'}</div>
+ </>
+ }>
+ <div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div>
+ </Tooltip>
</div>
<div className="ribbon-frameList">
{this.frameListHeader}
{this.frameList}
</div>
- <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(" TODO: play frames")}>Play</div>
+ <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(' TODO: play frames')}>
+ Play
+ </div>
</div>
</div>
</div>
@@ -1933,37 +2389,41 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
const val = String(color.hex);
- targetDoc["pres-text-color"] = val;
+ targetDoc['pres-text-color'] = val;
return true;
- }
+ };
@undoBatch
@action
switchPresented = (color: ColorState) => {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
const val = String(color.hex);
- targetDoc["pres-text-viewed-color"] = val;
+ targetDoc['pres-text-viewed-color'] = val;
return true;
- }
+ };
@computed get activeColorPicker() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- return !this.openActiveColorPicker ? (null) : <SketchPicker onChange={this.switchActive}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
- '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
- '#FFFFFF', '#f1efeb', 'transparent']}
- color={StrCast(targetDoc["pres-text-color"])} />;
+ return !this.openActiveColorPicker ? null : (
+ <SketchPicker
+ onChange={this.switchActive}
+ presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ color={StrCast(targetDoc['pres-text-color'])}
+ />
+ );
}
@computed get viewedColorPicker() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- return !this.openViewedColorPicker ? (null) : <SketchPicker onChange={this.switchPresented}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
- '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
- '#FFFFFF', '#f1efeb', 'transparent']}
- color={StrCast(targetDoc["pres-text-viewed-color"])} />;
+ return !this.openViewedColorPicker ? null : (
+ <SketchPicker
+ onChange={this.switchPresented}
+ presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ color={StrCast(targetDoc['pres-text-viewed-color'])}
+ />
+ );
}
@action
@@ -1974,7 +2434,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (srcContext) this.togglePath(srcContext, true);
}
// Turn off the progressivize editors for each document
- this.childDocs.forEach((doc) => {
+ this.childDocs.forEach(doc => {
doc.editSnapZoomProgressivize = false;
doc.editZoomProgressivize = false;
const targetDoc = Cast(doc.presentationTargetDoc, Doc, null);
@@ -1983,7 +2443,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// targetDoc.editScrollProgressivize = false;
}
});
- }
+ };
//Toggle whether the user edits or not
@action
@@ -1991,14 +2451,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
if (!targetDoc.editZoomProgressivize) {
- if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; targetDoc.zoomProgressivize = true;
+ if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true;
+ targetDoc.zoomProgressivize = true;
targetDoc.editZoomProgressivize = true;
activeItem.editZoomProgressivize = true;
} else {
targetDoc.editZoomProgressivize = false;
activeItem.editZoomProgressivize = false;
}
- }
+ };
//Toggle whether the user edits or not
@action
@@ -2006,12 +2467,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
if (!targetDoc.editScrollProgressivize) {
- if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; activeItem.scrollProgressivize = true; }
+ if (!targetDoc.scrollProgressivize) {
+ targetDoc.scrollProgressivize = true;
+ activeItem.scrollProgressivize = true;
+ }
targetDoc.editScrollProgressivize = true;
} else {
targetDoc.editScrollProgressivize = false;
}
- }
+ };
//Progressivize Zoom
@action
@@ -2027,7 +2491,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
targetDoc._currentFrame = 0;
targetDoc.lastFrame = 0;
}
- }
+ };
//Progressivize Zoom
@action
@@ -2043,7 +2507,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
targetDoc._currentFrame = 0;
targetDoc.lastFrame = 0;
}
- }
+ };
//Progressivize Child Docs
@action
@@ -2052,12 +2516,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetDoc: Doc = this.targetDoc;
targetDoc._currentFrame = targetDoc.lastFrame;
if (!targetDoc.editProgressivize) {
- if (!activeItem.presProgressivize) { activeItem.presProgressivize = true; targetDoc.presProgressivize = true; }
+ if (!activeItem.presProgressivize) {
+ activeItem.presProgressivize = true;
+ targetDoc.presProgressivize = true;
+ }
targetDoc.editProgressivize = true;
} else {
targetDoc.editProgressivize = false;
}
- }
+ };
@action
progressivizeChild = (e: React.MouseEvent) => {
@@ -2079,40 +2546,48 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
targetDoc._currentFrame = 0;
targetDoc.keyFrameEditing = true;
}
- }
+ };
@action
checkMovementLists = (doc: Doc, xlist: any, ylist: any) => {
const x: List<number> = xlist;
const y: List<number> = ylist;
const tags: JSX.Element[] = [];
- let pathPoints = ""; //List of all of the pathpoints that need to be added
+ let pathPoints = ''; //List of all of the pathpoints that need to be added
for (let i = 0; i < x.length - 1; i++) {
if (y[i] || x[i]) {
- if (i === 0) pathPoints = (x[i] - 11) + "," + (y[i] + 33);
- else pathPoints = pathPoints + " " + (x[i] - 11) + "," + (y[i] + 33);
- tags.push(<div className="progressivizeMove-frame" style={{ position: 'absolute', top: y[i], left: x[i] }}>{i}</div>);
+ if (i === 0) pathPoints = x[i] - 11 + ',' + (y[i] + 33);
+ else pathPoints = pathPoints + ' ' + (x[i] - 11) + ',' + (y[i] + 33);
+ tags.push(
+ <div className="progressivizeMove-frame" style={{ position: 'absolute', top: y[i], left: x[i] }}>
+ {i}
+ </div>
+ );
}
}
- tags.push(<svg style={{ overflow: 'visible', position: 'absolute' }}><polyline
- points={pathPoints}
- style={{
- position: 'absolute',
- opacity: 1,
- stroke: "#000000",
- strokeWidth: 2,
- strokeDasharray: '10 5',
- }}
- fill="none"
- /></svg>);
+ tags.push(
+ <svg style={{ overflow: 'visible', position: 'absolute' }}>
+ <polyline
+ points={pathPoints}
+ style={{
+ position: 'absolute',
+ opacity: 1,
+ stroke: '#000000',
+ strokeWidth: 2,
+ strokeDasharray: '10 5',
+ }}
+ fill="none"
+ />
+ </svg>
+ );
return tags;
- }
+ };
@observable
toggleDisplayMovement = (doc: Doc) => {
if (doc.displayMovement) doc.displayMovement = false;
else doc.displayMovement = true;
- }
+ };
@action
checkList = (doc: Doc, list: any): number => {
@@ -2124,22 +2599,54 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1];
return x[NumCast(doc._currentFrame)];
} else return 100;
- }
+ };
@computed get progressivizeChildDocs() {
const targetDoc: Doc = this.targetDoc;
const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
const tags: JSX.Element[] = [];
docs.forEach((doc, index) => {
- if (doc["x-indexed"] && doc["y-indexed"]) {
- tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>);
+ if (doc['x-indexed'] && doc['y-indexed']) {
+ tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? 'block' : 'none' }}>{this.checkMovementLists(doc, doc['x-indexed'], doc['y-indexed'])}</div>);
}
tags.push(
- <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}>
- <div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div>
- <div className="progressivizeButton-frame">{doc.appearFrame}</div>
- <div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div>
- </div>);
+ <div
+ className="progressivizeButton"
+ key={index}
+ onPointerLeave={() => {
+ if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0;
+ }}
+ onPointerOver={() => {
+ if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5;
+ }}
+ onClick={e => {
+ this.toggleDisplayMovement(doc);
+ e.stopPropagation();
+ }}
+ style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : '#c8c8c8', top: NumCast(doc.y), left: NumCast(doc.x) }}>
+ <div className="progressivizeButton-prev">
+ <FontAwesomeIcon
+ icon={'caret-left'}
+ size={'lg'}
+ onClick={e => {
+ e.stopPropagation();
+ this.prevAppearFrame(doc, index);
+ }}
+ />
+ </div>
+ <div className="progressivizeButton-frame">{NumCast(doc.appearFrame)}</div>
+ <div className="progressivizeButton-next">
+ <FontAwesomeIcon
+ icon={'caret-right'}
+ size={'lg'}
+ onClick={e => {
+ e.stopPropagation();
+ this.nextAppearFrame(doc, index);
+ }}
+ />
+ </div>
+ </div>
+ );
});
return tags;
}
@@ -2148,25 +2655,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
nextAppearFrame = (doc: Doc, i: number): void => {
// const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
// const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
- const appearFrame = Cast(doc.appearFrame, "number", null);
+ const appearFrame = Cast(doc.appearFrame, 'number', null);
if (appearFrame === undefined) {
doc.appearFrame = 0;
}
doc.appearFrame = appearFrame + 1;
- this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame));
- }
+ this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame));
+ };
@action
prevAppearFrame = (doc: Doc, i: number): void => {
// const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
// const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null);
- const appearFrame = Cast(doc.appearFrame, "number", null);
+ const appearFrame = Cast(doc.appearFrame, 'number', null);
if (appearFrame === undefined) {
doc.appearFrame = 0;
}
doc.appearFrame = Math.max(0, appearFrame - 1);
- this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame));
- }
+ this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame));
+ };
@action
updateOpacityList = (list: any, frame: number) => {
@@ -2191,10 +2698,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
list = x;
}
- }
+ };
@computed get moreInfoDropdown() {
- return (<div></div>);
+ return <div></div>;
}
@computed
@@ -2205,33 +2712,41 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth > 0) {
- CurrentUserUtils.propertiesWidth = 0;
+ if (SettingsManager.propertiesWidth > 0) {
+ SettingsManager.propertiesWidth = 0;
} else {
- CurrentUserUtils.propertiesWidth = 250;
+ SettingsManager.propertiesWidth = 250;
}
- }
+ };
@computed get toolbar() {
- const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left";
- const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel";
+ const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+ const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
- const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation);
+ const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.ActivePresentation;
const activeColor = Colors.LIGHT_BLUE;
const inactiveColor = Colors.WHITE;
- return (mode === CollectionViewType.Carousel3D) ? (null) : (
+ return mode === CollectionViewType.Carousel3D ? null : (
<div id="toolbarContainer" className={'presBox-toolbar'}>
{/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
<FontAwesomeIcon icon={"plus"} />
<FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
</div></Tooltip> */}
- <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}>
- <div style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}>
- <FontAwesomeIcon icon={"exchange-alt"} />
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'View paths'}</div>
+ </>
+ }>
+ <div
+ style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }}
+ className={'toolbar-button'}
+ onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}>
+ <FontAwesomeIcon icon={'exchange-alt'} />
</div>
</Tooltip>
- {isMini ? (null) :
+ {isMini ? null : (
<>
<div className="toolbar-divider" />
{/* <Tooltip title={<><div className="dash-tooltip">{this._expandBoolean ? "Minimize all" : "Expand all"}</div></>}>
@@ -2242,18 +2757,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
<div className="toolbar-divider" /> */}
- <Tooltip title={<><div className="dash-tooltip">{presKeyEvents ? "Keys are active" : "Keys are not active - click anywhere on the presentation trail to activate keys"}</div></>}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>
+ </>
+ }>
<div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
- <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"keyboard"} style={{ color: presKeyEvents ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: presKeyEvents ? activeColor : inactiveColor }} />
</div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{propTitle}</div></>}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{propTitle}</div>
+ </>
+ }>
<div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
- <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+ <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.propertiesWidth > 0 ? activeColor : inactiveColor }} />
</div>
</Tooltip>
</>
- }
+ )}
</div>
);
}
@@ -2267,34 +2792,45 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
return (
- <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? "none" : undefined }}>
- {isMini || Doc.UserDoc().noviceMode ? (null) : <select className="presBox-viewPicker"
- style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
- onPointerDown={e => e.stopPropagation()}
- onChange={this.viewChanged}
- value={mode}>
- <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
- <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>
- </select>}
+ <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? 'none' : undefined }}>
+ {isMini ? null : (
+ <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presStatus === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}>
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>
+ List
+ </option>
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}>
+ Tree
+ </option>
+ {Doc.noviceMode ? null : (
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>
+ 3D Carousel
+ </option>
+ )}
+ </select>
+ )}
<div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}>
- <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}>
- <div className="presBox-button-left"
+ <span className={`presBox-button ${this.layoutDoc.presStatus === 'edit' ? 'present' : ''}`}>
+ <div
+ className="presBox-button-left"
onClick={undoBatch(() => {
if (this.childDocs.length) {
- this.layoutDoc.presStatus = "manual";
+ this.layoutDoc.presStatus = 'manual';
this.gotoDocument(this.itemIndex, this.activeItem);
}
})}>
- <FontAwesomeIcon icon={"play-circle"} />
- <div style={{ display: this.props.PanelWidth() > 200 ? "inline-flex" : "none" }}>&nbsp; Present</div>
+ <FontAwesomeIcon icon={'play-circle'} />
+ <div style={{ display: this.props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
</div>
- {(mode === CollectionViewType.Carousel3D || isMini) ? (null) : <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`}
- onClick={(action(() => {
- if (this.childDocs.length) this.presentTools = !this.presentTools;
- }))}>
- <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} />
- {this.presentDropdown}
- </div>}
+ {mode === CollectionViewType.Carousel3D || isMini ? null : (
+ <div
+ className={`presBox-button-right ${this.presentTools ? 'active' : ''}`}
+ onClick={action(() => {
+ if (this.childDocs.length) this.presentTools = !this.presentTools;
+ })}>
+ <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} />
+ {this.presentDropdown}
+ </div>
+ )}
</span>
{this.playButtons}
</div>
@@ -2306,7 +2842,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
getList = (list: any): List<number> => {
const x: List<number> = list;
return x;
- }
+ };
@action
updateList = (list: any): List<number> => {
@@ -2315,7 +2851,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
x.length + 1;
x[x.length - 1] = NumCast(targetDoc._scrollY);
return x;
- }
+ };
@action
newFrame = () => {
@@ -2324,7 +2860,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const type: string = StrCast(targetDoc.type);
if (!activeItem.frameList) activeItem.frameList = new List<number>();
switch (type) {
- case (DocumentType.PDF || DocumentType.RTF || DocumentType.WEB):
+ case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB:
this.updateList(activeItem.frameList);
break;
case DocumentType.COL:
@@ -2332,20 +2868,46 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
default:
break;
}
- }
+ };
@computed get frameListHeader() {
- return (<div className="frameList-header">
- &nbsp; Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : (null)}
- <div className={"frameList-headerButtons"}>
- <Tooltip title={<><div className="dash-tooltip">{"Add frame by example"}</div></>}><div className={"headerButton"} onClick={e => { e.stopPropagation(); this.newFrame(); }}>
- <FontAwesomeIcon icon={"plus"} onPointerDown={e => e.stopPropagation()} />
- </div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Edit in collection"}</div></>}><div className={"headerButton"} onClick={e => { e.stopPropagation(); console.log('New frame'); }}>
- <FontAwesomeIcon icon={"edit"} onPointerDown={e => e.stopPropagation()} />
- </div></Tooltip>
+ return (
+ <div className="frameList-header">
+ &nbsp; Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : null}
+ <div className={'frameList-headerButtons'}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Add frame by example'}</div>
+ </>
+ }>
+ <div
+ className={'headerButton'}
+ onClick={e => {
+ e.stopPropagation();
+ this.newFrame();
+ }}>
+ <FontAwesomeIcon icon={'plus'} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </Tooltip>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Edit in collection'}</div>
+ </>
+ }>
+ <div
+ className={'headerButton'}
+ onClick={e => {
+ e.stopPropagation();
+ console.log('New frame');
+ }}>
+ <FontAwesomeIcon icon={'edit'} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </Tooltip>
+ </div>
</div>
- </div>);
+ );
}
@computed get frameList() {
@@ -2353,66 +2915,175 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetDoc: Doc = this.targetDoc;
const frameList: List<number> = this.getList(activeItem.frameList);
if (frameList) {
- const frameItems = frameList.map((value) =>
- <div className="framList-item">
-
- </div>
- );
- return (
-
- <div className="frameList-container">
- {frameItems}
- </div>
- );
- } else return (null);
-
+ const frameItems = frameList.map(value => <div className="framList-item"></div>);
+ return <div className="frameList-container">{frameItems}</div>;
+ } else return null;
}
@computed get playButtonFrames() {
const targetDoc: Doc = this.targetDoc;
return (
<>
- {this.targetDoc ? <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}>
- <div>{targetDoc._currentFrame}</div>
- <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div>
- <div>{targetDoc.lastFrame}</div>
- </div> : null}
+ {this.targetDoc ? (
+ <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? 'inline-flex' : 'none' }}>
+ <div>{NumCast(targetDoc._currentFrame)}</div>
+ <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div>
+ <div>{NumCast(targetDoc.lastFrame)}</div>
+ </div>
+ ) : null}
</>
);
}
@computed get playButtons() {
- const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1);
- const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
+ const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
+ const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
// Case 1: There are still other frames and should go through all frames before going to next slide
- return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
- <div className="presPanel-divider"></div>
- <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
- <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? "pause" : "play"} /></div></Tooltip>
- <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onClick={() => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-right"} /></div>
- <div className="presPanel-divider"></div>
- <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}><b>1</b></div></Tooltip>
- <div
- className="presPanel-button-text"
- onClick={() => this.gotoDocument(0, this.activeItem)}
- style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}>
- Slide {this.itemIndex + 1} / {this.childDocs.length}
- {this.playButtonFrames}
+ return (
+ <div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== 'edit' ? 'inline-flex' : 'none' }}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Loop'}</div>
+ </>
+ }>
+ <div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop)}>
+ <FontAwesomeIcon icon={'redo-alt'} />
+ </div>
+ </Tooltip>
+ <div className="presPanel-divider"></div>
+ <div
+ className="presPanel-button"
+ style={{ opacity: presStart ? 0.4 : 1 }}
+ onClick={() => {
+ this.back();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ }}>
+ <FontAwesomeIcon icon={'arrow-left'} />
+ </div>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>
+ </>
+ }>
+ <div className="presPanel-button" onClick={this.startOrPause}>
+ <FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'pause' : 'play'} />
+ </div>
+ </Tooltip>
+ <div
+ className="presPanel-button"
+ style={{ opacity: presEnd ? 0.4 : 1 }}
+ onClick={() => {
+ this.next();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ }}>
+ <FontAwesomeIcon icon={'arrow-right'} />
+ </div>
+ <div className="presPanel-divider"></div>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Click to return to 1st slide'}</div>
+ </>
+ }>
+ <div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}>
+ <b>1</b>
+ </div>
+ </Tooltip>
+ <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
+ Slide {this.itemIndex + 1} / {this.childDocs.length}
+ {this.playButtonFrames}
+ </div>
+ <div className="presPanel-divider"></div>
+ {this.props.PanelWidth() > 250 ? (
+ <div
+ className="presPanel-button-text"
+ onClick={undoBatch(
+ action(() => {
+ this.layoutDoc.presStatus = 'edit';
+ clearTimeout(this._presTimer);
+ })
+ )}>
+ EXIT
+ </div>
+ ) : (
+ <div className="presPanel-button" onClick={undoBatch(action(() => (this.layoutDoc.presStatus = 'edit')))}>
+ <FontAwesomeIcon icon={'times'} />
+ </div>
+ )}
</div>
- <div className="presPanel-divider"></div>
- {this.props.PanelWidth() > 250 ? <div className="presPanel-button-text" onClick={undoBatch(action(() => { this.layoutDoc.presStatus = "edit"; clearTimeout(this._presTimer); }))}>EXIT</div>
- : <div className="presPanel-button" onClick={undoBatch(action(() => this.layoutDoc.presStatus = "edit"))}>
- <FontAwesomeIcon icon={"times"} />
- </div>}
- </div>);
+ );
}
@action
startOrPause = () => {
if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex);
else this.pauseAutoPres();
- }
+ };
+
+ @action
+ prevClicked = (e: PointerEvent) => {
+ this.back();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ };
+
+ @action
+ nextClicked = (e: PointerEvent) => {
+ this.next();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ };
+ @undoBatch
+ @action
+ exitClicked = (e: PointerEvent) => {
+ this.updateMinimize();
+ this.layoutDoc.presStatus = PresStatus.Edit;
+ clearTimeout(this._presTimer);
+ };
+
+ @action
+ startMarqueeCreateSlide = () => {
+ PresBox.startMarquee = true;
+ };
+
+ AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ var indexNum = 0;
+ for (let i = 0; i < index.length; i++) {
+ indexNum += index[i] * 10 ** -i;
+ }
+ if (this._treeViewMap.get(treeViewDoc) !== indexNum) {
+ this._treeViewMap.set(treeViewDoc, indexNum);
+ const sorted = this.sort(this._treeViewMap);
+ const curList = DocListCast(this.dataDoc[this.presFieldKey]);
+ if (sorted.length !== curList.length || sorted.some((doc, ind) => doc !== curList[ind])) {
+ this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
+ }
+ }
+ return this.childDocs;
+ };
+
+ RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ if (!this._unmounting && this.isTree) {
+ this._treeViewMap.delete(treeViewDoc);
+ this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
+ }
+ return this.childDocs;
+ };
+
+ // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1])
+ sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
render() {
// calling this method for keyEvents
@@ -2420,60 +3091,113 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// needed to ensure that the childDocs are loaded for looking up fields
this.childDocs.slice();
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
- const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation);
- const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1);
- const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
- return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ?
- <div className="miniPres">
- <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.ActivePresentation;
+ const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
+ const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
+ return DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc) ? (
+ <div className="miniPres" onClick={e => e.stopPropagation()}>
+ <div className="presPanelOverlay" style={{ display: 'inline-flex', height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Loop'}</div>
+ </>
+ }>
+ <div
+ className="presPanel-button"
+ style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
+ <FontAwesomeIcon icon={'redo-alt'} />
+ </div>
+ </Tooltip>
<div className="presPanel-divider"></div>
- <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
- <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
- <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onClick={() => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-right"} /></div>
+ <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
+ <FontAwesomeIcon icon={'arrow-left'} />
+ </div>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>
+ </>
+ }>
+ <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}>
+ <FontAwesomeIcon icon={this.layoutDoc.presStatus === 'auto' ? 'pause' : 'play'} />
+ </div>
+ </Tooltip>
+ <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}>
+ <FontAwesomeIcon icon={'arrow-right'} />
+ </div>
<div className="presPanel-divider"></div>
- <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}><b>1</b></div></Tooltip>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Click to return to 1st slide'}</div>
+ </>
+ }>
+ <div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}>
+ <b>1</b>
+ </div>
+ </Tooltip>
<div className="presPanel-button-text">
Slide {this.itemIndex + 1} / {this.childDocs.length}
{this.playButtonFrames}
</div>
- <div className="presPanel-divider"></div>
- <div className="presPanel-button-text" onClick={undoBatch(action(() => { this.updateMinimize(); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); }))}>EXIT</div>
+ <div className="presPanel-divider" />
+ <div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>
+ EXIT
+ </div>
</div>
</div>
- :
- <div className="presBox-cont" style={{ minWidth: CurrentUserUtils.OverlayDocs.includes(this.layoutDoc) ? 240 : undefined }} >
+ ) : (
+ <div className="presBox-cont" style={{ minWidth: DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc) ? 240 : undefined }}>
{this.topPanel}
{this.toolbar}
{this.newDocumentToolbarDropdown}
<div className="presBox-listCont">
- {mode !== CollectionViewType.Invalid ?
- <CollectionView {...this.props}
- ContainingCollectionDoc={this.props.Document}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.panelHeight}
- childIgnoreNativeSize={true}
- moveDocument={returnFalse}
- childFitWidth={returnTrue}
- childOpacity={returnOne}
- childLayoutTemplate={this.childLayoutTemplate}
- filterAddDocument={this.addDocumentFilter}
- removeDocument={returnFalse}
- dontRegisterView={true}
- focus={this.selectElement}
- ScreenToLocalTransform={this.getTransform}
- />
- : (null)
+ <div className="Slide" style={{ height: `calc(100% - 30px)` }}>
+ {mode !== CollectionViewType.Invalid ? (
+ <CollectionView
+ {...this.props}
+ ContainingCollectionDoc={this.props.Document}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.panelHeight}
+ childIgnoreNativeSize={true}
+ moveDocument={returnFalse}
+ childFitWidth={returnTrue}
+ childOpacity={returnOne}
+ childLayoutTemplate={this.childLayoutTemplate}
+ filterAddDocument={this.addDocumentFilter}
+ removeDocument={returnFalse}
+ dontRegisterView={true}
+ focus={this.selectElement}
+ scriptContext={this}
+ ScreenToLocalTransform={this.getTransform}
+ AddToMap={this.AddToMap}
+ RemFromMap={this.RemFromMap}
+ hierarchyIndex={[]}
+ />
+ ) : null}
+ </div>
+
+ {
+ // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack
+ <Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}>
+ <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}>
+ + NEW SLIDE
+ </button>
+ </Tooltip>
}
</div>
- </div>;
+ </div>
+ );
}
}
-ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) {
- if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data);
- if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 35 : 31;
- if (field === 'presStatus') return container.presStatus;
- if (field === '_itemIndex') return container._itemIndex;
- if (field === 'presBox') return container;
- return undefined;
-}); \ No newline at end of file
+
+ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
+ const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null);
+ const openInTab = (doc: Doc, finished?: () => void) => {
+ CollectionDockingView.AddSplit(doc, 'right');
+ finished?.();
+ };
+ DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom));
+});
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 1ad4b820e..969f034a8 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -5,155 +5,157 @@ $slide-background: #d5dce2;
$slide-active: #5B9FDD;
.presItem-container {
- cursor: grab;
- display: grid;
- grid-template-columns: 20px auto;
- font-family: Roboto;
- letter-spacing: normal;
- position: relative;
- pointer-events: all;
- width: 100%;
- height: 100%;
- font-weight: 400;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- align-items: center;
+ cursor: grab;
+ display: flex;
+ grid-template-columns: 20px auto;
+ font-family: Roboto;
+ letter-spacing: normal;
+ position: relative;
+ pointer-events: all;
+ width: 100%;
+ height: 100%;
+ font-weight: 400;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ align-items: center;
- .presItem-number {
- margin-top: 3.5px;
- font-size: 12px;
- font-weight: 700;
- text-align: center;
- justify-self: center;
- align-self: flex-start;
- position: relative;
- display: inline-block;
- overflow: hidden;
- }
+ // .presItem-number {
+ // margin-top: 3.5px;
+ // font-size: 12px;
+ // font-weight: 700;
+ // text-align: center;
+ // justify-self: center;
+ // align-self: flex-start;
+ // position: relative;
+ // display: inline-block;
+ // overflow: hidden;
+ // }
}
.presItem-slide {
- position: relative;
- background-color: #d5dce2;
- border-radius: 5px;
- height: calc(100% - 7px);
- width: calc(100% - 15px);
- display: grid;
- grid-template-rows: 16px 10px auto;
- grid-template-columns: max-content max-content max-content max-content auto;
+ position: relative;
+ height: 100%;
+ width: 100%;
+ border-bottom: .5px solid grey;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
- .presItem-name {
- min-width: 20px;
- z-index: 300;
- top: 2px;
- align-self: center;
- font-size: 11px;
- font-family: Roboto;
- font-weight: 500;
- position: relative;
- padding-left: 10px;
- padding-right: 10px;
- letter-spacing: normal;
- width: max-content;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: pre;
- }
-
- .presItem-docName {
- min-width: 20px;
- z-index: 300;
- align-self: center;
- font-size: 9px;
- font-family: Roboto;
- font-weight: 300;
- position: relative;
- padding-left: 10px;
- padding-right: 10px;
- letter-spacing: normal;
- width: max-content;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: pre;
- grid-row: 2;
- grid-column: 1/6;
- }
-
- .presItem-time {
- align-self: center;
- position: relative;
- padding-right: 10px;
- top: 1px;
- font-size: 10;
- font-weight: 300;
- font-family: Roboto;
- z-index: 300;
- letter-spacing: normal;
- }
-
- .presItem-embedded {
- overflow: hidden;
- grid-row: 3;
- grid-column: 1/8;
- position: relative;
- display: flex;
- width: auto;
- justify-content: center;
- margin: auto;
- margin-bottom: 2px;
- border-bottom-left-radius: 5px;
- border-bottom-right-radius: 5px;
- }
-
- .presItem-embeddedMask {
- width: 100%;
- height: 100%;
- position: absolute;
- border-radius: 3px;
- top: 0;
- left: 0;
- z-index: 1;
- overflow: hidden;
- }
-
-
- .presItem-slideButtons {
- display: flex;
- grid-column: 7;
- grid-row: 1/3;
- width: max-content;
- justify-self: right;
- justify-content: flex-end;
-
- .slideButton {
- cursor: pointer;
+ .presItem-name {
+ display: flex;
+ min-width: 20px;
+ z-index: 300;
+ top: 2px;
+ align-self: center;
+ font-size: 11px;
+ font-family: Roboto;
+ font-weight: 500;
position: relative;
- border-radius: 100px;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ }
+
+ .presItem-docName {
+ min-width: 20px;
z-index: 300;
- width: 18px;
- height: 18px;
- display: flex;
- font-size: 12px;
- justify-self: center;
align-self: center;
- background-color: rgba(0, 0, 0, 0.5);
- color: white;
+ font-size: 9px;
+ font-family: Roboto;
+ font-weight: 300;
+ position: relative;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ grid-row: 2;
+ grid-column: 1/6;
+ }
+
+ .presItem-time {
+ align-self: center;
+ position: relative;
+ padding-right: 10px;
+ top: 1px;
+ font-size: 10;
+ font-weight: 300;
+ font-family: Roboto;
+ z-index: 300;
+ letter-spacing: normal;
+ }
+
+ .presItem-embedded {
+ overflow: hidden;
+ grid-row: 3;
+ grid-column: 1/8;
+ position: relative;
+ display: flex;
+ width: auto;
justify-content: center;
- align-items: center;
- transition: 0.2s;
- margin-right: 3px;
- }
-
- .slideButton:hover {
- background-color: rgba(0, 0, 0, 1);
- transform: scale(1.2);
- }
- }
+ margin: auto;
+ margin-bottom: 2px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ }
+
+ .presItem-embeddedMask {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ border-radius: 3px;
+ top: 0;
+ left: 0;
+ z-index: 1;
+ overflow: hidden;
+ }
+
+
+ .presItem-slideButtons {
+ display: flex;
+ grid-column: 7;
+ grid-row: 1/3;
+ width: max-content;
+ justify-self: right;
+ justify-content: flex-end;
+
+ .slideButton {
+ cursor: pointer;
+ position: relative;
+ border-radius: 100px;
+ z-index: 300;
+ width: 18px;
+ height: 18px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+ }
+
+ .slideButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+ }
+ }
}
// .presItem-slide:hover {
@@ -192,44 +194,116 @@ $slide-active: #5B9FDD;
// }
.presItem-slide.active {
- box-shadow: 0 0 0px 1.5px $dark-blue;
+ box-shadow: 0 0 0px 2.5px $dark-blue;
}
-.presItem-multiDrag {
- font-family: Roboto;
- font-weight: 600;
- color: white;
- text-align: center;
- justify-content: center;
- align-content: center;
- width: 100px;
- height: 30px;
+.presItem-slide.group {
+ border-radius: 5px;
+}
+
+.presItem-slide.activeGroup {
+ border-radius: 5px;
+ box-shadow: 0 0 0px 2.5px $dark-blue;
+}
+
+.presItem-groupSlideContainer {
position: absolute;
- background-color: $dark-blue;
- z-index: 4000;
- border-radius: 10px;
- box-shadow: black 0.4vw 0.4vw 0.8vw;
- line-height: 30px;
+ /* grid-row: 3; */
+ /* grid-column: 1/8; */
+ top: 28;
+ display: block;
+ background: #92adb9;
+ width: 100%;
+ height: 10px;
+ border-radius: 0px 0px 5px 5px;
+ height: calc(100% - 28px);
+ display: grid;
+ grid-template-rows: auto auto auto;
+ grid-template-columns: 100%;
+ justify-items: left;
+ align-items: center;
}
-.presItem-miniSlide {
- font-weight: 700;
- font-size: 12;
- grid-column: 1/8;
- align-self: center;
- justify-self: center;
+.presItem-groupSlide {
+ position: relative;
background-color: #d5dce2;
- width: 26px;
- text-align: center;
- height: 26px;
- line-height: 28px;
- border-radius: 100%;
+ border-radius: 5px;
+ height: calc(100% - 7px);
+ width: calc(100% - 20px);
+ left: 15px;
+ /* height: 20px; */
+ /* width: calc(100% - 19px); */
+ display: flex;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
+}
+
+.presItem-multiDrag {
+ font-family: Roboto;
+ font-weight: 600;
+ color: white;
+ text-align: center;
+ justify-content: center;
+ align-content: center;
+ width: 100px;
+ height: 30px;
+ position: absolute;
+ background-color: $dark-blue;
+ z-index: 4000;
+ border-radius: 10px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+ line-height: 30px;
+}
+
+.presItem-miniSlide {
+ font-weight: 700;
+ font-size: 12;
+ grid-column: 1/8;
+ align-self: center;
+ justify-self: center;
+ background-color: #d5dce2;
+ width: 26px;
+ text-align: center;
+ height: 26px;
+ line-height: 28px;
+ border-radius: 100%;
}
.presItem-miniSlide.active {
- box-shadow: 0 0 0px 1.5px $dark-blue;
+ box-shadow: 0 0 0px 1.5px $dark-blue;
}
-// .presItem-slide:hover {
-// background: $slide-hover;
-// } \ No newline at end of file
+.expandButton {
+ cursor: pointer;
+ position: absolute;
+ border-radius: 100px;
+ bottom: 0;
+ left: -18;
+ z-index: 300;
+ width: 15px;
+ height: 15px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: #92adb9;
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+}
+
+.expandButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+}
+
+.presItem-groupNum {
+ color: #d5dce2;
+ position: absolute;
+ left: -15px;
+ top: 1;
+ font-weight: 600;
+ font-size: 12;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 5f252c3e9..0cf15d297 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,50 +1,66 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { DataSym, Doc, Opt } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils";
-import { DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager } from "../../../util/DragManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { List } from '../../../../fields/List';
+import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager } from '../../../util/DragManager';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
import { ViewBoxBaseComponent } from '../../DocComponent';
-import { EditableView } from "../../EditableView";
-import { Colors } from "../../global/globalEnums";
-import { DocumentView, DocumentViewProps } from "../../nodes/DocumentView";
+import { EditableView } from '../../EditableView';
+import { Colors } from '../../global/globalEnums';
+import { DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../../nodes/FieldView';
-import { StyleProp } from "../../StyleProvider";
-import { PresBox } from "./PresBox";
-import "./PresElementBox.scss";
-import { PresMovement } from "./PresEnums";
-import React = require("react");
+import { StyleProp } from '../../StyleProvider';
+import { PresBox } from './PresBox';
+import './PresElementBox.scss';
+import { PresMovement } from './PresEnums';
+import React = require('react');
/**
* This class models the view a document added to presentation will have in the presentation.
* It involves some functionality for its buttons and options.
*/
@observer
export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(PresElementBox, fieldKey);
+ }
_heightDisposer: IReactionDisposer | undefined;
@observable _dragging = false;
- // these fields are conditionally computed fields on the layout document that take this document as a parameter
- @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
- @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
- @computed get presStatus() { return StrCast(this.lookupField("presStatus")); }
- @computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
- @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); }
- @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
+ // Idea: this boolean will determine whether to automatically show the video when this preselement is selected.
+ // @observable static showVideo: boolean = false;
+ @computed get indexInPres() {
+ return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
+ } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
+ @computed get collapsedHeight() {
+ return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31;
+ } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
+ @computed get presStatus() {
+ return this.presBox.presStatus;
+ }
+ @computed get presBox() {
+ return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!;
+ }
+ @computed get targetDoc() {
+ return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc;
+ }
componentDidMount() {
this.layoutDoc.hideLinkButton = true;
- this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
- params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
+ this._heightDisposer = reaction(
+ () => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
+ params => (this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0)),
+ { fireImmediately: true }
+ );
}
componentWillUnmount() {
this._heightDisposer?.();
@@ -58,28 +74,27 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
presExpandDocumentClick = () => {
this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton;
- }
+ };
embedHeight = (): number => 97;
// embedWidth = () => this.props.PanelWidth();
// embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
embedWidth = (): number => this.props.PanelWidth() - 35;
- styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (property === StyleProp.Opacity) return 1;
return this.props.styleProvider?.(doc, props, property);
- }
+ };
/**
* The function that is responsible for rendering a preview or not for this
* presentation element.
*/
@computed get renderEmbeddedInline() {
- return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
+ return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
<div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
<DocumentView
- Document={this.targetDoc}
- DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+ Document={this.rootDoc}
+ DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
styleProvider={this.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={returnEmptyDoclist}
rootSelected={returnTrue}
addDocument={returnFalse}
@@ -87,6 +102,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
isContentActive={this.props.isContentActive}
addDocTab={returnFalse}
pinToPres={returnFalse}
+ fitContentsToBox={returnTrue}
PanelWidth={this.embedWidth}
PanelHeight={this.embedHeight}
ScreenToLocalTransform={Transform.Identity}
@@ -103,29 +119,62 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
hideLinkButton={true}
/>
<div className="presItem-embeddedMask" />
- </div>;
+ </div>
+ );
}
+ @computed get renderGroupSlides() {
+ const childDocs = DocListCast(this.targetDoc.data);
+ const groupSlides = childDocs.map((doc: Doc, ind: number) => (
+ <div
+ className="presItem-groupSlide"
+ onClick={e => {
+ console.log('Clicked on slide with index: ', ind);
+ e.stopPropagation();
+ e.preventDefault();
+ PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ this.presExpandDocumentClick();
+ }}>
+ <div className="presItem-groupNum">{`${ind + 1}.`}</div>
+ {/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */}
+ <div className="presItem-name">
+ <EditableView
+ ref={this._titleRef}
+ editing={undefined}
+ contents={doc.title}
+ overflow={'ellipsis'}
+ GetValue={() => StrCast(doc.title)}
+ SetValue={(value: string) => {
+ doc.title = !value.trim().length ? '-untitled-' : value;
+ return true;
+ }}
+ />
+ </div>
+ </div>
+ ));
+ return groupSlides;
+ }
@computed get duration() {
let durationInS: number;
- if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; }
- else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
+ if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) {
+ durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime);
+ durationInS = Math.round(durationInS * 10) / 10;
+ } else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
else durationInS = 2;
- return "D: " + durationInS + "s";
+ return 'D: ' + durationInS + 's';
}
@computed get transition() {
let transitionInS: number;
if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000;
else transitionInS = 0.5;
- return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? (null) : "M: " + transitionInS + "s";
+ return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
}
private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
private _titleRef: React.RefObject<EditableView> = React.createRef();
-
@action
headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
const element = e.target as any;
@@ -133,59 +182,71 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
e.preventDefault();
if (element && !(e.ctrlKey || e.metaKey)) {
if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
- PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
+ PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
} else {
- setupMoveUpEvents(this, e, ((e: PointerEvent) => {
- PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
- return this.startDrag(e);
- }), emptyFunction, emptyFunction);
+ setupMoveUpEvents(
+ this,
+ e,
+ (e: PointerEvent) => {
+ PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
+ return this.startDrag(e);
+ },
+ emptyFunction,
+ emptyFunction
+ );
}
}
- }
-
- headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
- e.stopPropagation();
- e.preventDefault();
- }
+ };
/**
* Function to drag and drop the pres element to a diferent location
*/
- //TODO: this is what you need to look into
startDrag = (e: PointerEvent) => {
const miniView: boolean = this.toolbarWidth <= 100;
const activeItem = this.rootDoc;
const dragArray = PresBox.Instance._dragArray;
const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
+ if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
+ dragData.dropAction = 'move';
+ dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
+ dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
const dragItem: HTMLElement[] = [];
if (dragArray.length === 1) {
- const doc = dragArray[0];
- doc.className = miniView ? "presItem-miniSlide" : "presItem-slide";
- dragItem.push(doc);
+ const doc = this._itemRef.current || dragArray[0];
+ if (doc) {
+ doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide';
+ dragItem.push(doc);
+ }
} else if (dragArray.length >= 1) {
const doc = document.createElement('div');
- doc.className = "presItem-multiDrag";
- doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides";
+ doc.className = 'presItem-multiDrag';
+ doc.innerText = 'Move ' + PresBox.Instance._selectedArray.size + ' slides';
doc.style.position = 'absolute';
- doc.style.top = (e.clientY) + 'px';
- doc.style.left = (e.clientX - 50) + 'px';
+ doc.style.top = e.clientY + 'px';
+ doc.style.left = e.clientX - 50 + 'px';
dragItem.push(doc);
}
// const dropEvent = () => runInAction(() => this._dragging = false);
if (activeItem) {
- DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined);
+ DragManager.StartDocumentDrag(
+ dragItem.map(ele => ele),
+ dragData,
+ e.clientX,
+ e.clientY,
+ undefined
+ );
// runInAction(() => this._dragging = true);
return true;
}
return false;
- }
+ };
onPointerOver = (e: any) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointermove', this.onPointerMove);
+ };
onPointerMove = (e: PointerEvent) => {
const slide = this._itemRef.current!;
@@ -195,77 +256,181 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (slide && dragIsPresItem) {
const rect = slide.getBoundingClientRect();
- const y = e.clientY - rect.top; //y position within the element.
+ const y = e.clientY - rect.top; //y position within the element.
const height = slide.clientHeight;
const halfLine = height / 2;
if (y <= halfLine) {
slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
- slide.style.borderBottom = "0px";
+ slide.style.borderBottom = '0px';
} else if (y > halfLine) {
- slide.style.borderTop = "0px";
+ slide.style.borderTop = '0px';
slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
}
}
- document.removeEventListener("pointermove", this.onPointerMove);
- }
+ document.removeEventListener('pointermove', this.onPointerMove);
+ };
onPointerLeave = (e: any) => {
- this._itemRef.current!.style.borderTop = "0px";
- this._itemRef.current!.style.borderBottom = "0px";
- document.removeEventListener("pointermove", this.onPointerMove);
- }
+ this._itemRef.current!.style.borderTop = '0px';
+ this._itemRef.current!.style.borderBottom = '0px';
+ document.removeEventListener('pointermove', this.onPointerMove);
+ };
@action
toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth < 5) {
- action(() => (CurrentUserUtils.propertiesWidth = 250));
+ if (SettingsManager.propertiesWidth < 5) {
+ action(() => (SettingsManager.propertiesWidth = 250));
}
- }
+ };
@undoBatch
removeItem = action((e: React.MouseEvent) => {
+ e.stopPropagation();
this.props.removeDocument?.(this.rootDoc);
if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
PresBox.Instance._selectedArray.delete(this.rootDoc);
}
- e.stopPropagation();
+ this.removeAllRecordingInOverlay();
});
// set the value/title of the individual pres element
@undoBatch
@action
onSetValue = (value: string) => {
- this.rootDoc.title = !value.trim().length ? "-untitled-" : value;
+ this.rootDoc.title = !value.trim().length ? '-untitled-' : value;
return true;
- }
+ };
/**
* Method called for updating the view of the currently selected document
- *
- * @param targetDoc
- * @param activeItem
+ *
+ * @param targetDoc
+ * @param activeItem
*/
@undoBatch
@action
updateView = (targetDoc: Doc, activeItem: Doc) => {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const clipWidth = targetDoc._clipWidth;
- activeItem.presPinClipWidth = clipWidth;
- } else {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
+ switch (targetDoc.type) {
+ case DocumentType.PDF:
+ case DocumentType.WEB:
+ case DocumentType.RTF:
+ const scroll = targetDoc._scrollTop;
+ activeItem.presPinViewScroll = scroll;
+ break;
+ case DocumentType.VID:
+ case DocumentType.AUDIO:
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ break;
+ case DocumentType.COMPARISON:
+ const clipWidth = targetDoc._clipWidth;
+ activeItem.presPinClipWidth = clipWidth;
+ break;
+ default:
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeItem.presPinViewX = x;
+ activeItem.presPinViewY = y;
+ activeItem.presPinViewScale = scale;
}
+ };
+
+ @computed get recordingIsInOverlay() {
+ let isInOverlay = false;
+ DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
+ if (doc.slides === this.rootDoc) {
+ isInOverlay = true;
+ // return
+ }
+ });
+ return isInOverlay;
}
+ // a previously recorded video will have timecode defined
+ static videoIsRecorded = (activeItem: Doc) => {
+ const casted = Cast(activeItem.recording, Doc, null);
+ return casted && 'currentTimecode' in casted;
+ };
+
+ removeAllRecordingInOverlay = () => {
+ DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
+ if (doc.slides === this.rootDoc) {
+ Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc);
+ }
+ });
+ };
+
+ static removeEveryExistingRecordingInOverlay = () => {
+ // Remove every recording that already exists in overlay view
+ DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
+ // if it's a recording video, don't remove from overlay (user can lose data)
+ if (!PresElementBox.videoIsRecorded(DocCast(doc.slides))) return;
+
+ if (doc.slides !== null) {
+ Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc);
+ }
+ });
+ };
+
+ @undoBatch
+ @action
+ hideRecording = (e: React.MouseEvent, iconClick: boolean = false) => {
+ e.stopPropagation();
+ this.removeAllRecordingInOverlay();
+
+ // if (iconClick) PresElementBox.showVideo = false;
+ };
+
+ @undoBatch
+ @action
+ showRecording = (activeItem: Doc, iconClick: boolean = false) => {
+ // if (iconClick) PresElementBox.showVideo = true;
+ // if (!PresElementBox.showVideo) return;
+
+ // remove the overlays on switch *IF* not opened from the specific icon
+ if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay();
+
+ if (activeItem.recording) {
+ Doc.AddDocToList(Doc.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null));
+ }
+ };
+
+ @undoBatch
+ @action
+ startRecording = (activeItem: Doc) => {
+ console.log('start recording', 'activeItem', activeItem);
+ if (PresElementBox.videoIsRecorded(activeItem)) {
+ // if we already have an existing recording
+ this.showRecording(activeItem, true);
+ // // if we already have an existing recording
+ // Doc.AddDocToList(Doc.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null));
+ } else {
+ // Remove every recording that already exists in overlay view
+ // this is a design decision to clear to focus in on the recoding mode
+ PresElementBox.removeEveryExistingRecordingInOverlay();
+
+ // if we dont have any recording
+ const recording = Docs.Create.WebCamDocument('', {
+ _width: 384,
+ _height: 216,
+ hideDocumentButtonBar: true,
+ hideDecorationTitle: true,
+ hideOpenButton: true,
+ // hideDeleteButton: true,
+ cloneFieldFilter: new List<string>(['system']),
+ });
+
+ // attach the recording to the slide, and attach the slide to the recording
+ recording.slides = activeItem;
+ activeItem.recording = recording;
+
+ // make recording box appear in the bottom right corner of the screen
+ recording.x = window.innerWidth - recording[WidthSym]() - 20;
+ recording.y = window.innerHeight - recording[HeightSym]() - 20;
+ Doc.AddDocToList(Doc.MyOverlayDocs, undefined, recording);
+ }
+ };
+
@computed
get toolbarWidth(): number {
const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
@@ -275,29 +440,31 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return width;
}
- // TODO: this is what you were looking for
@computed get mainItem() {
- const isSelected: boolean = PresBox.Instance._selectedArray.has(this.rootDoc);
+ const isSelected: boolean = PresBox.Instance?._selectedArray.has(this.rootDoc);
const toolbarWidth: number = this.toolbarWidth;
const showMore: boolean = this.toolbarWidth >= 300;
const miniView: boolean = this.toolbarWidth <= 110;
const presBox: Doc = this.presBox; //presBox
const presBoxColor: string = StrCast(presBox._backgroundColor);
- const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false;
+ const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
const targetDoc: Doc = this.targetDoc;
const activeItem: Doc = this.rootDoc;
+
return (
- <div className={`presItem-container`}
+ <div
+ className={`presItem-container`}
key={this.props.Document[Id] + this.indexInPres}
ref={this._itemRef}
style={{
- backgroundColor: presColorBool ? isSelected ? "rgba(250,250,250,0.3)" : "transparent" : isSelected ? Colors.LIGHT_BLUE : "transparent",
- opacity: this._dragging ? 0.3 : 1
+ backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
+ opacity: this._dragging ? 0.3 : 1,
}}
onClick={e => {
e.stopPropagation();
e.preventDefault();
PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ this.showRecording(activeItem);
}}
onDoubleClick={action(e => {
this.toggleProperties();
@@ -305,73 +472,126 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
})}
onPointerOver={this.onPointerOver}
onPointerLeave={this.onPointerLeave}
- onPointerDown={this.headerDown}
- onPointerUp={this.headerUp}
- >
- {miniView ?
- // when width is LESS than 110 px
- <div className={`presItem-miniSlide ${isSelected ? "active" : ""}`} ref={miniView ? this._dragRef : null}>
- {`${this.indexInPres + 1}.`}
- </div>
- :
- // when width is MORE than 110 px
- <div className="presItem-number">
- {`${this.indexInPres + 1}.`}
- </div>}
- {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
- style={{
- backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
- boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
- }}>
- <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
- <EditableView
- ref={this._titleRef}
- editing={!isSelected ? false : undefined}
- contents={activeItem.title}
- overflow={'ellipsis'}
- GetValue={() => StrCast(activeItem.title)}
- SetValue={this.onSetValue}
- />
- </div>
- <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip>
- <div className={"presItem-slideButtons"}>
- <Tooltip title={<><div className="dash-tooltip">{"Update view"}</div></>}>
- <div className="slideButton"
- onClick={() => this.updateView(targetDoc, activeItem)}
- style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V</div>
- </Tooltip>
- {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
- <div className="slideButton"
- onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp}
- style={{
- zIndex: 1000 - this.indexInPres,
- fontWeight: 700,
- backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined,
- height: activeItem.groupWithUp ? 53 : 18,
- transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined
- }}>
- <div style={{ transform: activeItem.groupWithUp ? "rotate(180deg) translate(0, -17.5px)" : "rotate(0deg)" }}>
- <FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} />
+ onPointerDown={this.headerDown}>
+ {/* {miniView ?
+ // when width is LESS than 110 px
+ <div className={`presItem-miniSlide ${isSelected ? "active" : ""}`} ref={miniView ? this._dragRef : null}>
+ {`${this.indexInPres + 1}.`}
+ </div>
+ :
+ // when width is MORE than 110 px
+ <div className="presItem-number">
+ {`${this.indexInPres + 1}.`}
+ </div>} */}
+ {/* <div className="presItem-number">
+ {`${this.indexInPres + 1}.`}
+ </div> */}
+ {miniView ? null : (
+ <div
+ ref={miniView ? null : this._dragRef}
+ className={`presItem-slide ${isSelected ? 'active' : ''}`}
+ style={{
+ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+ boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isSelected ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
+ }}>
+ <div className="presItem-name" style={{ maxWidth: showMore ? toolbarWidth - 195 : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <div>{`${this.indexInPres + 1}. `}</div>
+ <EditableView ref={this._titleRef} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
+ </div>
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
+ <div className={'presItem-slideButtons'}>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Update view'}</div>
+ </>
+ }>
+ <div className="slideButton" onClick={() => this.updateView(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinView ? 'flex' : 'none' }}>
+ V
+ </div>
+ </Tooltip>
+
+ {this.recordingIsInOverlay ? (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Hide Recording'}</div>
+ </>
+ }>
+ <div className="slideButton" onClick={e => this.hideRecording(e, true)} style={{ fontWeight: 700 }}>
+ <FontAwesomeIcon icon={'video-slash'} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </Tooltip>
+ ) : (
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{`${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>
+ </>
+ }>
+ <div
+ className="slideButton"
+ onClick={e => {
+ e.stopPropagation();
+ this.startRecording(activeItem);
+ }}
+ style={{ fontWeight: 700 }}>
+ <FontAwesomeIcon icon={'video'} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </Tooltip>
+ )}
+
+ {/* {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
+ <div className="slideButton"
+ onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp}
+ style={{
+ zIndex: 1000 - this.indexInPres,
+ fontWeight: 700,
+ backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined,
+ height: activeItem.groupWithUp ? 53 : 18,
+ transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined
+ }}>
+ <div style={{ transform: activeItem.groupWithUp ? "rotate(180deg) translate(0, -17.5px)" : "rotate(0deg)" }}>
+ <FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </div>
+ </Tooltip>} */}
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}</div>
+ </>
+ }>
+ <div
+ className={'slideButton'}
+ onClick={e => {
+ e.stopPropagation();
+ this.presExpandDocumentClick();
+ }}>
+ <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </Tooltip>
+ <Tooltip
+ title={
+ <>
+ <div className="dash-tooltip">{'Remove from presentation'}</div>
+ </>
+ }>
+ <div className={'slideButton'} onClick={this.removeItem}>
+ <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
</div>
- </div>
- </Tooltip>}
- <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"slideButton"} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}>
- <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? "eye-slash" : "eye"} onPointerDown={e => e.stopPropagation()} />
- </div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div
- className={"slideButton"}
- onClick={this.removeItem}>
- <FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} />
- </div></Tooltip>
+ </Tooltip>
+ </div>
+ {/* <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div> */}
+ {this.renderEmbeddedInline}
</div>
- <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div>
- {this.renderEmbeddedInline}
- </div>}
- </div >);
+ )}
+ </div>
+ );
}
render() {
- return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : this.mainItem;
+ return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index ad3afb775..1a1120b6c 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,16 +1,16 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, observable, IReactionDisposer, reaction, ObservableMap } from "mobx";
-import { observer } from "mobx-react";
-import { ColorState } from "react-color";
-import { Doc, Opt } from "../../../fields/Doc";
-import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from "../../../Utils";
-import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
-import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
-import "./AnchorMenu.scss";
-import { SelectionManager } from "../../util/SelectionManager";
-import { LinkPopup } from "../linking/LinkPopup";
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { ColorState } from 'react-color';
+import { Doc, Opt } from '../../../fields/Doc';
+import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
+import { SelectionManager } from '../../util/SelectionManager';
+import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
+import { LinkPopup } from '../linking/LinkPopup';
+import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
+import './AnchorMenu.scss';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -19,35 +19,38 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
private _disposer: IReactionDisposer | undefined;
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 = "";
+ '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 private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable private _showLinkPopup: boolean = false;
@observable public Highlighting: boolean = false;
- @observable public Status: "marquee" | "annotation" | "" = "";
+ @observable public Status: 'marquee' | 'annotation' | '' = '';
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
+ public OnCrop: (e: PointerEvent) => void = unimplementedFunction;
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
+ public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined;
public GetAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
public Delete: () => void = unimplementedFunction;
@@ -55,7 +58,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public PinToPres: () => void = unimplementedFunction;
public MakePushpin: () => void = unimplementedFunction;
public IsPushpin: () => boolean = returnFalse;
- public get Active() { return this._left > 0; }
+ public get Active() {
+ return this._left > 0;
+ }
constructor(props: Readonly<{}>) {
super(props);
@@ -65,19 +70,40 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
}
componentDidMount() {
- this._disposer = reaction(() => SelectionManager.Views(),
+ this._disposer = reaction(
+ () => SelectionManager.Views(),
selected => {
this._showLinkPopup = false;
AnchorMenu.Instance.fadeOut(true);
- });
+ }
+ );
}
pointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, (e: PointerEvent) => {
- this.StartDrag(e, this._commentCont.current!);
- return true;
- }, returnFalse, e => this.OnClick?.(e));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ (e: PointerEvent) => {
+ this.StartDrag(e, this._commentCont.current!);
+ return true;
+ },
+ returnFalse,
+ e => this.OnClick?.(e)
+ );
+ };
+
+ cropDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ (e: PointerEvent) => {
+ this.StartCropDrag(e, this._commentCont.current!);
+ return true;
+ },
+ returnFalse,
+ e => this.OnCrop?.(e)
+ );
+ };
@action
highlightClicked = (e: React.MouseEvent) => {
@@ -85,41 +111,45 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this.Highlighting = !this.Highlighting;
}
AnchorMenu.Instance.fadeOut(true);
- }
+ };
@action
toggleLinkPopup = (e: React.MouseEvent) => {
//ignore the potential null type error because this method cannot be called unless the user selects text and clicks the link button
//change popup visibility field to visible
this._showLinkPopup = !this._showLinkPopup;
- }
+ };
@computed get highlighter() {
- const button =
+ const button = (
<button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
- <div className="anchor-color-preview" >
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
+ <div className="anchor-color-preview">
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: this.Highlighting ? '' : 'rotate(-45deg)' }} />
<div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
</div>
- </button>;
+ </button>
+ );
- const dropdownContent =
+ 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>;
+ 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>;
+ </div>
+ );
return (
- <Tooltip key="highlighter" title={<div className="dash-tooltip">{"Click to Highlight"}</div>}>
+ <Tooltip key="highlighter" title={<div className="dash-tooltip">{'Click to Highlight'}</div>}>
<div className="anchorMenu-highlighter">
- <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} pdf={true} />
+ <ButtonDropdown key={'highlighter'} button={button} dropdownContent={dropdownContent} pdf={true} />
</div>
</Tooltip>
);
@@ -127,65 +157,87 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@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: "",
+ 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);
- }
-
- @action keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => { this._keyValue = e.currentTarget.value; };
- @action valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => { this._valueValue = e.currentTarget.value; };
+ };
+
+ @action keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._keyValue = e.currentTarget.value;
+ };
+ @action valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._valueValue = e.currentTarget.value;
+ };
@action addTag = (e: React.PointerEvent) => {
if (this._keyValue.length > 0 && this._valueValue.length > 0) {
this._added = this.AddTag(this._keyValue, this._valueValue);
- setTimeout(action(() => this._added = false), 1000);
+ setTimeout(
+ action(() => (this._added = false)),
+ 1000
+ );
}
- }
+ };
render() {
- const buttons = this.Status === "marquee" ?
- [
- this.highlighter,
-
- <Tooltip key="annotate" title={<div className="dash-tooltip">{"Drag to Place Annotation"}</div>}>
- <button className="antimodeMenu-button annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: "grab" }}>
- <FontAwesomeIcon icon="comment-alt" size="lg" />
- </button>
- </Tooltip>,
- //NOTE: link popup is currently in progress
- <Tooltip key="link" title={<div className="dash-tooltip">{"Find document to link to selected text"}</div>}>
- <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
- <FontAwesomeIcon style={{ position: "absolute", transform: "scale(1.5)" }} icon={"search"} size="lg" />
- <FontAwesomeIcon style={{ position: "absolute", transform: "scale(0.5)", transformOrigin: "top left", top: 12, left: 12 }} icon={"link"} size="lg" />
- </button>
- </Tooltip>,
- <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />
- ] : [
- <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.Delete}>
- <FontAwesomeIcon icon="trash-alt" size="lg" />
- </button>
- </Tooltip>,
- <Tooltip key="Pin" title={<div className="dash-tooltip">{"Pin to Presentation"}</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
- <FontAwesomeIcon icon="map-pin" size="lg" />
- </button>
- </Tooltip>,
- <Tooltip key="pushpin" title={<div className="dash-tooltip">{"toggle pushpin behavior"}</div>}>
- <button className="antimodeMenu-button" style={{ color: this.IsPushpin() ? "black" : "white", backgroundColor: this.IsPushpin() ? "white" : "black" }} onPointerDown={this.MakePushpin}>
- <FontAwesomeIcon icon="thumbtack" size="lg" />
- </button>
- </Tooltip>,
- // <div key="7" className="anchorMenu-addTag" >
- // <input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
- // <input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
- // </div>,
- // <button key="8" className="antimodeMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
- // <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
- ];
+ const buttons =
+ this.Status === 'marquee'
+ ? [
+ this.highlighter,
+
+ <Tooltip key="annotate" title={<div className="dash-tooltip">{'Drag to Place Annotation'}</div>}>
+ <button className="antimodeMenu-button annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="comment-alt" size="lg" />
+ </button>
+ </Tooltip>,
+ //NOTE: link popup is currently in progress
+ <Tooltip key="link" title={<div className="dash-tooltip">{'Find document to link to selected text'}</div>}>
+ <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'top left', top: 12, left: 12 }} icon={'link'} size="lg" />
+ </button>
+ </Tooltip>,
+ <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />,
+ AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? (
+ <></>
+ ) : (
+ <Tooltip key="crop" title={<div className="dash-tooltip">{'Click/Drag to create cropped image'}</div>}>
+ <button className="antimodeMenu-button annotate" onPointerDown={this.cropDown} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="image" size="lg" />
+ </button>
+ </Tooltip>
+ ),
+ ]
+ : [
+ <Tooltip key="trash" title={<div className="dash-tooltip">{'Remove Link Anchor'}</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.Delete}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" />
+ </button>
+ </Tooltip>,
+ <Tooltip key="Pin" title={<div className="dash-tooltip">{'Pin to Presentation'}</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
+ <FontAwesomeIcon icon="map-pin" size="lg" />
+ </button>
+ </Tooltip>,
+ <Tooltip key="pushpin" title={<div className="dash-tooltip">{'toggle pushpin behavior'}</div>}>
+ <button className="antimodeMenu-button" style={{ color: this.IsPushpin() ? 'black' : 'white', backgroundColor: this.IsPushpin() ? 'white' : 'black' }} onPointerDown={this.MakePushpin}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" />
+ </button>
+ </Tooltip>,
+ // <div key="7" className="anchorMenu-addTag" >
+ // <input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
+ // <input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
+ // </div>,
+ // <button key="8" className="antimodeMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
+ // <FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
+ ];
return this.getElement(buttons);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index b1d1d8293..44f815336 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,52 +1,46 @@
-import React = require("react");
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { LinkManager } from "../../util/LinkManager";
-import { undoBatch } from "../../util/UndoManager";
-import { FieldViewProps } from "../nodes/FieldView";
-import { AnchorMenu } from "./AnchorMenu";
-import "./Annotation.scss";
+import React = require('react');
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { LinkFollower } from '../../util/LinkFollower';
+import { undoBatch } from '../../util/UndoManager';
+import { FieldViewProps } from '../nodes/FieldView';
+import { AnchorMenu } from './AnchorMenu';
+import './Annotation.scss';
interface IAnnotationProps extends FieldViewProps {
anno: Doc;
dataDoc: Doc;
fieldKey: string;
showInfo: (anno: Opt<Doc>) => void;
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
}
@observer
-export
- class Annotation extends React.Component<IAnnotationProps> {
+export class Annotation extends React.Component<IAnnotationProps> {
render() {
- return DocListCast(this.props.anno.textInlineAnnotations).map(a => <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />);
+ return (
+ <div>
+ {DocListCast(this.props.anno.textInlineAnnotations).map(a => (
+ <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
+ ))}
+ </div>
+ );
}
}
interface IRegionAnnotationProps extends IAnnotationProps {
document: Doc;
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
}
@observer
class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
- private _disposers: { [name: string]: IReactionDisposer } = {};
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- @observable _brushed: boolean = false;
- @computed get annoTextRegion() { return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document; }
-
- componentDidMount() {
- this._disposers.brush = reaction(
- () => this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion),
- brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0)
- );
- }
-
- componentWillUnmount() {
- Object.values(this._disposers).forEach(disposer => disposer?.());
+ @computed get annoTextRegion() {
+ return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document;
}
@undoBatch
@@ -55,20 +49,20 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
this.props.dataDoc[this.props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
AnchorMenu.Instance.fadeOut(true);
this.props.select(false);
- }
+ };
@undoBatch
- pinToPres = () => this.props.pinToPres(this.annoTextRegion)
+ pinToPres = () => this.props.pinToPres(this.annoTextRegion);
@undoBatch
- makePushpin = () => this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin
+ makePushpin = () => (this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin);
isPushpin = () => BoolCast(this.annoTextRegion.isPushpin);
@action
onPointerDown = (e: React.PointerEvent) => {
if (e.button === 2 || e.ctrlKey) {
- AnchorMenu.Instance.Status = "annotation";
+ AnchorMenu.Instance.Status = 'annotation';
AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
AnchorMenu.Instance.Pinned = false;
AnchorMenu.Instance.AddTag = this.addTag.bind(this);
@@ -77,30 +71,43 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
AnchorMenu.Instance.IsPushpin = this.isPushpin;
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
e.stopPropagation();
- }
- else if (e.button === 0) {
+ } else if (e.button === 0) {
e.stopPropagation();
- LinkManager.FollowLink(undefined, this.annoTextRegion, this.props, false);
+ LinkFollower.FollowLink(undefined, this.annoTextRegion, this.props, false);
}
- }
+ };
addTag = (key: string, value: string): boolean => {
const valNum = parseInt(value);
this.annoTextRegion[key] = isNaN(valNum) ? value : valNum;
return true;
- }
+ };
render() {
- return (<div className="htmlAnnotation" onPointerEnter={() => this.props.showInfo(this.props.anno)} onPointerLeave={() => this.props.showInfo(undefined)} onPointerDown={this.onPointerDown} ref={this._mainCont}
- style={{
- left: NumCast(this.props.document.x),
- top: NumCast(this.props.document.y),
- width: NumCast(this.props.document._width),
- height: NumCast(this.props.document._height),
- opacity: this._brushed ? 0.5 : undefined,
- pointerEvents: this.props.pointerEvents as any,
- backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor),
- }} >
- </div>);
+ const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
+ return (
+ <div
+ className="htmlAnnotation"
+ ref={this._mainCont}
+ onPointerEnter={action(() => {
+ Doc.BrushDoc(this.props.anno);
+ this.props.showInfo(this.props.anno);
+ })}
+ onPointerLeave={action(() => {
+ Doc.UnBrushDoc(this.props.anno);
+ this.props.showInfo(undefined);
+ })}
+ onPointerDown={this.onPointerDown}
+ style={{
+ left: NumCast(this.props.document.x),
+ top: NumCast(this.props.document.y),
+ width: NumCast(this.props.document._width),
+ height: NumCast(this.props.document._height),
+ opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
+ pointerEvents: this.props.pointerEvents?.() as any,
+ outline: brushed === Doc.DocBrushStatus.linkHighlighted ? 'solid 1px lightBlue' : undefined,
+ backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this.props.document.backgroundColor),
+ }}></div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 390aed1e0..822af6a68 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -1,5 +1,3 @@
-
-
.pdfViewer-content {
height: 100%;
width: 100%;
@@ -8,33 +6,42 @@
top: 0;
left: 0;
}
-.pdfViewerDash, .pdfViewerDash-interactive {
+
+.pdfViewerDash,
+.pdfViewerDash-interactive {
position: absolute;
width: 100%;
height: 100%;
top: 0;
- left:0;
+ left: 0;
position: absolute;
overflow-y: auto;
overflow-x: hidden;
transform-origin: top left;
-
+
// .canvasWrapper {
// transform: scale(0.75);
// transform-origin: top left;
// }
.textLayer {
opacity: unset;
- mix-blend-mode: multiply;// bcz: makes text fuzzy!
+ mix-blend-mode: multiply; // bcz: makes text fuzzy!
+
span {
padding-right: 5px;
padding-bottom: 4px;
}
}
- .textLayer ::selection { background: #ACCEF7; } // should match the backgroundColor in createAnnotation()
+
+ .textLayer ::selection {
+ background: #ACCEF7;
+ }
+
+ // should match the backgroundColor in createAnnotation()
.textLayer .highlight {
background-color: yellow;
}
+
.textLayer .highlight.selected {
background-color: orange;
}
@@ -43,32 +50,32 @@
position: relative;
border: unset;
}
+
.pdfViewerDash-text-selected {
- // position: relative; // bcz: this breaks auto-scrolling using the inline search box
+ // position: relative; // bcz: this breaks auto-scrolling using the inline search box
z-index: -1;
- .textLayer{
- pointer-events: all;
- user-select: text;
+
+ .textLayer {
+ pointer-events: all;
+ user-select: text;
}
}
+
.pdfViewerDash-text {
transform-origin: top left;
+
.textLayer {
will-change: transform;
}
}
- .pdfViewerDash-overlay, .pdfViewerDash-overlay-inking {
+ .pdfViewerDash-overlay {
transform-origin: left top;
position: absolute;
top: 0px;
left: 0px;
display: inline-block;
- width:100%;
- pointer-events: all;
- }
- .pdfViewerDash-overlay {
- pointer-events: none;
+ width: 100%;
}
.pdfViewerDash-overlayAnno {
@@ -81,7 +88,7 @@
border-radius: 5px;
display: block;
}
-
+
.pdfViewerDash-annotationLayer {
position: absolute;
transform-origin: left top;
@@ -90,12 +97,13 @@
pointer-events: none;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
}
+
.pdfViewerDash-waiting {
width: 70%;
height: 70%;
- margin : 15%;
+ margin: 15%;
transition: 0.4s opacity ease;
- opacity: 0.7;
+ opacity: 0.7;
position: absolute;
z-index: 10;
}
@@ -103,4 +111,4 @@
.pdfViewerDash-interactive {
pointer-events: all;
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 1856c5353..2c83082b7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,39 +1,35 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as Pdfjs from "pdfjs-dist";
-import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { InkTool } from "../../../fields/InkField";
-import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { PdfField } from "../../../fields/URLField";
-import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
-import { DocUtils } from "../../documents/Documents";
-import { Networking } from "../../Network";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { CompiledScript, CompileScript } from "../../util/Scripting";
-import { SelectionManager } from "../../util/SelectionManager";
-import { SharingManager } from "../../util/SharingManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { MarqueeOptionsMenu } from "../collections/collectionFreeForm";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
-import { DocumentViewProps } from "../nodes/DocumentView";
-import { FieldViewProps } from "../nodes/FieldView";
-import { LinkDocPreview } from "../nodes/LinkDocPreview";
-import { StyleProp } from "../StyleProvider";
-import { AnchorMenu } from "./AnchorMenu";
-import { Annotation } from "./Annotation";
-import "./PDFViewer.scss";
-import React = require("react");
-const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
-const pdfjsLib = require("pdfjs-dist");
-const _global = (window /* browser */ || global /* node */) as any;
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as Pdfjs from 'pdfjs-dist';
+import 'pdfjs-dist/web/pdf_viewer.css';
+import { Doc, DocListCast, Field, HeightSym, Opt } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SharingManager } from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { DocumentViewProps } from '../nodes/DocumentView';
+import { FieldViewProps } from '../nodes/FieldView';
+import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { StyleProp } from '../StyleProvider';
+import { AnchorMenu } from './AnchorMenu';
+import { Annotation } from './Annotation';
+import './PDFViewer.scss';
+import React = require('react');
+const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
+const pdfjsLib = require('pdfjs-dist');
+const _global = (window /* browser */ || global) /* node */ as any;
//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
-pdfjsLib.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@2.13.216/build/pdf.worker.js";
+pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.14.305/build/pdf.worker.js';
interface IViewerProps extends FieldViewProps {
Document: Doc;
@@ -43,11 +39,10 @@ interface IViewerProps extends FieldViewProps {
fieldKey: string;
pdf: Pdfjs.PDFDocumentProxy;
url: string;
- startupLive: boolean;
loaded?: (nw: number, nh: number, np: number) => void;
setPdfViewer: (view: PDFViewer) => void;
- ContentScaling?: () => number;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+ crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
}
/**
@@ -56,14 +51,11 @@ interface IViewerProps extends FieldViewProps {
@observer
export class PDFViewer extends React.Component<IViewerProps> {
static _annotationStyle: any = addStyleSheet();
- @observable private _pageSizes: { width: number, height: number }[] = [];
+ @observable private _pageSizes: { width: number; height: number }[] = [];
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
@observable private _marqueeing: number[] | undefined;
@observable private _textSelecting = true;
@observable private _showWaiting = true;
- @observable private _showCover = false;
- @observable private _zoomed = 1;
@observable private _overlayAnnoInfo: Opt<Doc>;
@observable private Index: number = -1;
@@ -74,105 +66,94 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _selectionText: string = "";
+ public _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
+ _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _selectionText: string = '';
private _downX: number = 0;
private _downY: number = 0;
- private _coverPath: any;
private _lastSearch = false;
private _viewerIsSetup = false;
private _ignoreScroll = false;
private _initialScroll: Opt<number>;
private _forcedScroll = true;
-
+ @observable isAnnotating = false;
// key where data is stored
@computed get allAnnotations() {
- return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined);
+ return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), this.props.docFilters(), this.props.docRangeFilters(), undefined);
+ }
+ @computed get inlineTextAnnotations() {
+ return this.allAnnotations.filter(a => a.textInlineAnnotations);
}
- @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
componentDidMount = async () => {
- // change the address to be the file address of the PNG version of each page
- // file address of the pdf
- const { url: { href } } = Cast(this.props.dataDoc[this.props.fieldKey], PdfField)!;
- const { url: relative } = this.props;
- if (relative.includes("/pdfs/")) {
- const pathComponents = relative.split("/pdfs/")[1].split("/");
- const coreFilename = pathComponents.pop()!.split(".")[0];
- const params: any = {
- coreFilename,
- pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))),
- };
- if (pathComponents.length) {
- params.subtree = `${pathComponents.join("/")}/`;
- }
- this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
- } else {
- const params: any = {
- coreFilename: relative.split("/")[relative.split("/").length - 1],
- pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))),
- };
- this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
- }
- runInAction(() => this._showWaiting = true);
- this.props.startupLive && this.setupPdfJsViewer();
- this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0);
+ runInAction(() => (this._showWaiting = true));
+ this.setupPdfJsViewer();
+ this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
- this._disposers.autoHeight = reaction(() => this.props.layoutDoc._autoHeight,
+ this._disposers.autoHeight = reaction(
+ () => this.props.layoutDoc._autoHeight,
autoHeight => {
if (autoHeight) {
- this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
- this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+ this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']);
+ this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
}
- });
+ }
+ );
- this._disposers.selected = reaction(() => this.props.isSelected(),
+ this._disposers.selected = reaction(
+ () => this.props.isSelected(),
selected => {
// if (!selected) {
// Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
// Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, []));
// }
- (SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
+ SelectionManager.Views().length === 1 && this.setupPdfJsViewer();
},
- { fireImmediately: true });
- this._disposers.curPage = reaction(() => Cast(this.props.Document._curPage, "number", null),
- (page) => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
{ fireImmediately: true }
);
- }
+ this._disposers.curPage = reaction(
+ () => Cast(this.props.Document._curPage, 'number', null),
+ page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
+ { fireImmediately: true }
+ );
+ };
componentWillUnmount = () => {
Object.values(this._disposers).forEach(disposer => disposer?.());
- document.removeEventListener("copy", this.copy);
- }
+ document.removeEventListener('copy', this.copy);
+ };
copy = (e: ClipboardEvent) => {
if (this.props.isContentActive() && e.clipboardData) {
- e.clipboardData.setData("text/plain", this._selectionText);
+ e.clipboardData.setData('text/plain', this._selectionText);
e.preventDefault();
}
- }
+ };
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
- this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
- await Promise.all(this._pageSizes.map((val, i) =>
- this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => {
- const page0or180 = page.rotate === 0 || page.rotate === 180;
- this._pageSizes.splice(i, 1, {
- width: (page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1]),
- height: (page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0])
- });
- if (i === this.props.pdf.numPages - 1) {
- this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
- page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], i);
- }
- }))));
- this.props.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72;
+ this._pageSizes = Array<{ width: number; height: number }>(this.props.pdf.numPages);
+ await Promise.all(
+ this._pageSizes.map((val, i) =>
+ this.props.pdf.getPage(i + 1).then(
+ action((page: Pdfjs.PDFPageProxy) => {
+ const page0or180 = page.rotate === 0 || page.rotate === 180;
+ this._pageSizes.splice(i, 1, {
+ width: page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
+ height: page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0],
+ });
+ if (i === this.props.pdf.numPages - 1) {
+ this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages);
+ }
+ })
+ )
+ )
+ );
+ this.props.Document.scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
}
- }
+ };
// scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location,
// otherwise it will scroll smoothly.
@@ -180,9 +161,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
if (doc !== this.props.rootDoc && mainCont) {
- const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
- const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
- if (scrollTo !== undefined) {
+ const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+ const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight));
+ if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
focusSpeed = 500;
if (!this._pdfViewer) this._initialScroll = scrollTo;
@@ -193,7 +174,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._initialScroll = NumCast(this.props.layoutDoc._scrollTop);
}
return focusSpeed;
- }
+ };
+ crop = (region: Doc | undefined, addCrop?: boolean) => {
+ return this.props.crop(region, addCrop);
+ };
@action
setupPdfJsViewer = async () => {
@@ -203,42 +187,34 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.props.setPdfViewer(this);
await this.initialLoad();
- this._disposers.filterScript = reaction(
- () => ScriptCast(this.props.Document.filterScript),
- action(scriptField => {
- const oldScript = this._script.originalScript;
- this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
- if (this._script.originalScript !== oldScript) {
- this.Index = -1;
- }
- }),
- { fireImmediately: true });
-
this.createPdfViewer();
- }
+ };
pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
- runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1);
+ runInAction(() => (this._pdfViewer.currentScaleValue = this.props.layoutDoc._viewScale = 1));
this.gotoPage(NumCast(this.props.Document._curPage, 1));
}
- document.removeEventListener("pagesinit", this.pagesinit);
- var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : "";
+ document.removeEventListener('pagesinit', this.pagesinit);
+ var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : '';
this._disposers.scroll = reaction(
() => Math.abs(NumCast(this.props.Document._scrollTop)),
- (pos) => {
+ pos => {
if (!this._ignoreScroll) {
- (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+ this._showWaiting && this.setupPdfJsViewer();
const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
this._forcedScroll = true;
if (duration) {
- setTimeout(() => {
- this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos);
- setTimeout(() => this._forcedScroll = false, duration);
- }, this._mainCont.current ? 0 : 250); // wait for mainCont and try again to scroll
+ setTimeout(
+ () => {
+ this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos);
+ setTimeout(() => (this._forcedScroll = false), duration);
+ },
+ this._mainCont.current ? 0 : 250
+ ); // wait for mainCont and try again to scroll
} else {
this._mainCont.current?.scrollTo({ top: pos });
this._forcedScroll = false;
@@ -252,23 +228,27 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll || 0) });
this._initialScroll = undefined;
}
- }
+ };
createPdfViewer() {
- if (!this._mainCont.current) { // bcz: I don't think this is ever triggered or needed
- console.log("PDFViewer- I guess we got here");
+ if (!this._mainCont.current) {
+ // bcz: I don't think this is ever triggered or needed
+ console.log('PDFViewer- I guess we got here');
if (this._retries < 5) {
this._retries++;
- console.log("PDFViewer- retry num:" + this._retries);
+ console.log('PDFViewer- retry num:' + this._retries);
setTimeout(() => this.createPdfViewer(), 1000);
}
return;
}
- document.removeEventListener("copy", this.copy);
- document.addEventListener("copy", this.copy);
+ document.removeEventListener('copy', this.copy);
+ document.addEventListener('copy', this.copy);
const eventBus = new PDFJSViewer.EventBus(true);
- eventBus._on("pagesinit", this.pagesinit);
- eventBus._on("pagerendered", action(() => this._showWaiting = false));
+ eventBus._on('pagesinit', this.pagesinit);
+ eventBus._on(
+ 'pagerendered',
+ action(() => (this._showWaiting = false))
+ );
const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
this._pdfViewer = new PDFJSViewer.PDFViewer({
@@ -276,33 +256,30 @@ export class PDFViewer extends React.Component<IViewerProps> {
viewer: this._viewer.current,
linkService: pdfLinkService,
findController: pdfFindController,
- renderer: "canvas",
- eventBus
+ renderer: 'canvas',
+ eventBus,
});
pdfLinkService.setViewer(this._pdfViewer);
pdfLinkService.setDocument(this.props.pdf, null);
this._pdfViewer.setDocument(this.props.pdf);
}
-
@action
prevAnnotation = () => {
this.Index = Math.max(this.Index - 1, 0);
this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]);
- }
+ };
@action
nextAnnotation = () => {
this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1);
this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]);
- }
+ };
@action
gotoPage = (p: number) => {
- if (this._pdfViewer?._setDocumentViewerElement?.offsetParent) {
- this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
- }
- }
+ this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
+ };
@action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
@@ -310,7 +287,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.scrollFocus(scrollToAnnotation, true);
Doc.linkFollowHighlight(scrollToAnnotation);
}
- }
+ };
+
+ @observable private _scrollTimer: any;
onScroll = (e: React.UIEvent<HTMLElement>) => {
if (this._mainCont.current && !this._forcedScroll) {
@@ -319,8 +298,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.props.layoutDoc._scrollTop = this._mainCont.current.scrollTop;
}
this._ignoreScroll = false;
+ if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
+ this._scrollTimer = setTimeout(() => {
+ DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false);
+ this._scrollTimer = undefined;
+ }, 200);
}
- }
+ };
// get the page index that the vertical offset passed in is on
getPageFromScroll = (vOffset: number) => {
@@ -330,7 +314,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
currOffset -= this._pageSizes[index++].height;
}
return index;
- }
+ };
@action
search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
@@ -339,22 +323,21 @@ export class PDFViewer extends React.Component<IViewerProps> {
findPrevious: bwd,
highlightAll: true,
phraseSearch: true,
- query: searchString
+ query: searchString,
};
if (clear) {
- this._pdfViewer?.findController.executeCommand('reset', { query: "" });
+ this._pdfViewer?.findController.executeCommand('reset', { query: '' });
} else if (!searchString) {
bwd ? this.prevAnnotation() : this.nextAnnotation();
} else if (this._pdfViewer?.pageViewsReady) {
this._pdfViewer.findController.executeCommand('findagain', findOpts);
- }
- else if (this._mainCont.current) {
+ } else if (this._mainCont.current) {
const executeFind = () => this._pdfViewer.findController.executeCommand('find', findOpts);
- this._mainCont.current.addEventListener("pagesloaded", executeFind);
- this._mainCont.current.addEventListener("pagerendered", executeFind);
+ this._mainCont.current.addEventListener('pagesloaded', executeFind);
+ this._mainCont.current.addEventListener('pagerendered', executeFind);
}
return true;
- }
+ };
@action
onPointerDown = (e: React.PointerEvent): void => {
@@ -371,46 +354,53 @@ export class PDFViewer extends React.Component<IViewerProps> {
if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
}
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
this.props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
- if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) {
+ this.isAnnotating = true;
+ if (e.target && ((e.target as any).className.includes('endOfContent') || (e.target as any).parentElement.className !== 'textLayer')) {
this._textSelecting = false;
- document.addEventListener("pointermove", this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
+ document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
} else {
// if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
- setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
-
- this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "htmlAnnotation", { "pointer-events": "none" });
- document.addEventListener("pointerup", this.onSelectEnd);
- document.addEventListener("pointermove", this.onSelectMove);
+ setTimeout(
+ action(() => (this._marqueeing = undefined)),
+ 100
+ ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+
+ this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
+ document.addEventListener('pointerup', this.onSelectEnd);
+ document.addEventListener('pointermove', this.onSelectMove);
}
}
- }
+ };
@action
finishMarquee = (x?: number, y?: number) => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this.isAnnotating = false;
this._marqueeing = undefined;
this._textSelecting = true;
- document.removeEventListener("pointermove", this.onSelectMove);
- }
+ document.removeEventListener('pointermove', this.onSelectMove);
+ };
onSelectMove = (e: PointerEvent) => e.stopPropagation();
@action
onSelectEnd = (e: PointerEvent): void => {
+ this.isAnnotating = false;
clearStyleSheetRules(PDFViewer._annotationStyle);
this.props.select(false);
- document.removeEventListener("pointermove", this.onSelectMove);
- document.removeEventListener("pointerup", this.onSelectEnd);
+ document.removeEventListener('pointermove', this.onSelectMove);
+ document.removeEventListener('pointerup', this.onSelectEnd);
const sel = window.getSelection();
- if (sel?.type === "Range") {
+ if (sel?.type === 'Range') {
this.createTextAnnotation(sel, sel.getRangeAt(0));
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
}
- }
+ };
@action
createTextAnnotation = (sel: Selection, selRange: Range) => {
@@ -419,57 +409,44 @@ export class PDFViewer extends React.Component<IViewerProps> {
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
- if (rect && rect.width !== this._mainCont.current.clientWidth) {
+ if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) {
const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
- const annoBox = document.createElement("div");
- annoBox.className = "marqueeAnnotator-annotationBox";
+ const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1);
+ const annoBox = document.createElement('div');
+ annoBox.className = 'marqueeAnnotator-annotationBox';
// transforms the positions from screen onto the pdf div
- annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString();
- annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString();
- annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString();
- annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString();
+ annoBox.style.top = (((rect.top - boundingRect.top) * scaleX) / pdfScale + this._mainCont.current.scrollTop).toString();
+ annoBox.style.left = (((rect.left - boundingRect.left) * scaleX) / pdfScale).toString();
+ annoBox.style.width = ((rect.width * this._mainCont.current.offsetWidth) / boundingRect.width / pdfScale).toString();
+ annoBox.style.height = ((rect.height * this._mainCont.current.offsetHeight) / boundingRect.height / pdfScale).toString();
this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
}
}
}
- this._selectionText = selRange.cloneContents().textContent || "";
+ this._selectionText = selRange.cloneContents().textContent || '';
// clear selection
- if (sel.empty) { // Chrome
+ if (sel.empty) {
+ // Chrome
sel.empty();
- } else if (sel.removeAllRanges) { // Firefox
+ } else if (sel.removeAllRanges) {
+ // Firefox
sel.removeAllRanges();
}
- }
+ };
scrollXf = () => {
return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform();
- }
+ };
onClick = (e: React.MouseEvent) => {
- if (this._setPreviewCursor && e.button === 0 &&
- Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
- Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
this._setPreviewCursor(e.clientX, e.clientY, false, false);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
- }
+ };
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
-
- getCoverImage = () => {
- if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) {
- setTimeout((() => {
- this.props.Document._height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
- Doc.SetNativeWidth(this.props.Document, (Doc.NativeWidth(this.props.Document) || 0) * this._coverPath.height / this._coverPath.width);
- }).bind(this), 0);
- }
- const nativeWidth = Doc.NativeWidth(this.props.Document);
- const nativeHeight = Doc.NativeHeight(this.props.Document);
- const resolved = Utils.prepend(this._coverPath.path);
- return <img key={resolved} src={resolved} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)}
- style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
- }
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
@action
onZoomWheel = (e: React.WheelEvent) => {
@@ -477,118 +454,145 @@ export class PDFViewer extends React.Component<IViewerProps> {
e.stopPropagation();
if (e.ctrlKey) {
const curScale = Number(this._pdfViewer.currentScaleValue);
- this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - curScale * e.deltaY / 1000));
- this._zoomed = Number(this._pdfViewer.currentScaleValue);
+ this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - (curScale * e.deltaY) / 1000));
+ this.props.layoutDoc._viewScale = Number(this._pdfViewer.currentScaleValue);
}
}
- }
+ };
- pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+ pointerEvents = () => (this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
@computed get annotationLayer() {
- const pe = this.pointerEvents();
- return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
- {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
- </div>;
+ return (
+ <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }} ref={this._annotationLayer}>
+ {this.inlineTextAnnotations
+ .sort((a, b) => NumCast(a.y) - NumCast(b.y))
+ .map(anno => (
+ <Annotation {...this.props} fieldKey={this.props.fieldKey + '-annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+ ))}
+ </div>
+ );
}
@computed get overlayInfo() {
- return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) :
+ return !this._overlayAnnoInfo ? null : (
<div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
<div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
- {this._overlayAnnoInfo.author + " " + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
+ {this._overlayAnnoInfo.author + ' ' + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
</div>
- </div>;
+ </div>
+ );
}
- showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
- overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
- panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
- panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
- basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
+ showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
+ overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1));
+ panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')];
transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
- childStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
- if (doc.textInlineAnnotations) return "none";
- return "all";
+ if (doc.textInlineAnnotations) return 'none';
+ return 'all';
}
return this.props.styleProvider?.(doc, props, property);
+ };
+
+ renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) => (
+ <CollectionFreeFormView
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ isAnnotationOverlay={true}
+ fieldKey={this.props.fieldKey + '-annotations'}
+ setPreviewCursor={this.setPreviewCursor}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this.panelWidth}
+ dropAction={'alias'}
+ select={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={docFilters || this.basicFilter}
+ styleProvider={this.childStyleProvider}
+ dontRenderDocuments={dontRender}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.overlayTransform}
+ renderDepth={this.props.renderDepth + 1}
+ />
+ );
+ @computed get overlayTransparentAnnotations() {
+ return this.renderAnnotations(this.transparentFilter, false);
+ }
+ @computed get overlayOpaqueAnnotations() {
+ return this.renderAnnotations(this.opaqueFilter, false);
+ }
+ @computed get overlayClickableAnnotations() {
+ return <div style={{ height: NumCast(this.props.rootDoc.scrollHeight) }}>{this.renderAnnotations(undefined, true)}</div>;
}
@computed get overlayLayer() {
- const renderAnnotations = (docFilters?: () => string[]) =>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- isAnnotationOverlay={true}
- fieldKey={this.props.fieldKey + "-annotations"}
- setPreviewCursor={this.setPreviewCursor}
- PanelHeight={this.panelHeight}
- PanelWidth={this.panelWidth}
- dropAction={"alias"}
- select={emptyFunction}
- ContentScaling={this.contentZoom}
- bringToFront={emptyFunction}
- docFilters={docFilters || this.basicFilter}
- styleProvider={this.childStyleProvider}
- dontRenderDocuments={docFilters ? false : true}
- CollectionView={undefined}
- ScreenToLocalTransform={this.overlayTransform}
- renderDepth={this.props.renderDepth + 1} />;
- return <div>
- <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
- style={{
- pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
- mixBlendMode: "multiply",
- transform: `scale(${this._zoomed})`
- }}>
- {renderAnnotations(this.transparentFilter)}
- </div>
- <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
- style={{
- pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
- mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined,
- transform: `scale(${this._zoomed})`
- }}>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
+ return (
+ <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
+ <div
+ className="pdfViewerDash-overlay"
+ style={{
+ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none',
+ mixBlendMode: 'multiply',
+ transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
+ }}>
+ {this.overlayTransparentAnnotations}
+ </div>
+ <div
+ className="pdfViewerDash-overlay"
+ style={{
+ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none',
+ mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined,
+ transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`,
+ }}>
+ {this.overlayOpaqueAnnotations}
+ {this.overlayClickableAnnotations}
+ </div>
</div>
- </div>;
+ );
}
@computed get pdfViewerDiv() {
- return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
- }
- @computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
- @computed get standinViews() {
- return <>
- {this._showCover ? this.getCoverImage() : (null)}
- {this._showWaiting ? <img className="pdfViewerDash-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
- </>;
+ return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
}
- contentZoom = () => this._zoomed;
+ savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
- return <div className="pdfViewer-content">
- <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
- onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
- style={{
- overflowX: this._zoomed !== 1 ? "scroll" : undefined,
- height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
- transform: `scale(${this.contentScaling})`
- }} >
- {this.pdfViewerDiv}
- {this.annotationLayer}
- {this.overlayLayer}
- {this.overlayInfo}
- {this.standinViews}
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.props.rootDoc} scrollTop={0} down={this._marqueeing}
- anchorMenuClick={this.props.anchorMenuClick}
- addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
- finishMarquee={this.finishMarquee}
- docView={this.props.docViewPath().lastElement()}
- getPageFromScroll={this.getPageFromScroll}
- savedAnnotations={this._savedAnnotations}
- annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ return (
+ <div className="pdfViewer-content">
+ <div
+ className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
+ ref={this._mainCont}
+ onScroll={this.onScroll}
+ onWheel={this.onZoomWheel}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ style={{
+ overflowX: NumCast(this.props.layoutDoc._viewScale, 1) !== 1 ? 'scroll' : undefined,
+ height: !this.props.Document._fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`,
+ }}>
+ {this.pdfViewerDiv}
+ {this.annotationLayer}
+ {this.overlayLayer}
+ {this.overlayInfo}
+ {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ <MarqueeAnnotator
+ rootDoc={this.props.rootDoc}
+ getPageFromScroll={this.getPageFromScroll}
+ anchorMenuClick={this.props.anchorMenuClick}
+ scrollTop={0}
+ down={this._marqueeing}
+ addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
+ docView={this.props.docViewPath().lastElement()}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ anchorMenuCrop={this._textSelecting ? undefined : this.crop}
+ />
+ )}
+ </div>
</div>
- </div>;
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 5fe2a5ab1..c5177de90 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,4 +1,4 @@
-import { Tooltip } from "@material-ui/core";
+import { Tooltip } from '@material-ui/core';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -6,13 +6,12 @@ import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../..
import { Id } from '../../../fields/FieldSymbols';
import { StrCast } from '../../../fields/Types';
import { DocUtils } from '../../documents/Documents';
-import { DocumentType } from "../../documents/DocumentTypes";
+import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { ViewBoxBaseComponent } from "../DocComponent";
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { ViewBoxBaseComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import "./SearchBox.scss";
-
+import './SearchBox.scss';
const DAMPENING_FACTOR = 0.9;
const MAX_ITERATIONS = 25;
@@ -29,13 +28,15 @@ export interface SearchBoxProps extends FieldViewProps {
*/
@observer
export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(SearchBox, fieldKey);
+ }
public static Instance: SearchBox;
private _inputRef = React.createRef<HTMLInputElement>();
- @observable _searchString = "";
- @observable _docTypeString = "all";
+ @observable _searchString = '';
+ @observable _docTypeString = 'all';
@observable _results: Map<Doc, string[]> = new Map<Doc, string[]>();
@observable _pageRanks: Map<Doc, number> = new Map<Doc, number>();
@observable _linkedDocsOut: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>();
@@ -52,7 +53,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
SearchBox.Instance = this;
}
- /**
+ /**
* This method is called when the SearchBox component is first mounted. When the user opens
* the search panel, the search input box is automatically selected. This allows the user to
* type in the search input box immediately, without needing clicking on it first.
@@ -75,7 +76,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* This method is called when the text in the search input box is modified by the user. The
* _searchString is updated to the new value of the text in the input box and submitSearch
* is called to update the search results accordingly.
- *
+ *
* (Note: There is no longer a need to press enter to submit a search. Any update to the input
* causes a search to be submitted automatically.)
*/
@@ -88,7 +89,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* This method is called when the option in the select drop-down menu is changed. The
* _docTypeString is updated to the new value of the option in the drop-down menu. This
* is used to filter the results of the search to documents of a specific type.
- *
+ *
* (Note: This doesn't affect the results array, so there is no need to submit a new
* search here. The results of the search on the _searchString query are simply filtered
* by type directly before rendering them.)
@@ -108,11 +109,12 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
});
+ // TODO: nda -- Change this method to change what happens when you click on the item.
makeLink = action((linkTo: Doc) => {
if (this.props.linkFrom) {
const linkFrom = this.props.linkFrom();
if (linkFrom) {
- DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }, "Link");
+ DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
}
}
});
@@ -127,12 +129,14 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
static foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
let newarray: Doc[] = [];
var depth = 0;
+ const visited: Doc[] = [];
while (docs.length > 0) {
newarray = [];
- docs.filter(d => d).forEach(d => {
+ docs.filter(d => d && !visited.includes(d)).forEach(d => {
+ visited.push(d);
const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
- const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const data = d[annos ? fieldKey + '-annotations' : fieldKey];
data && newarray.push(...DocListCast(data));
func(depth, d);
});
@@ -153,14 +157,18 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
var depth = 0;
while (docs.length > 0) {
newarray = [];
- await Promise.all(docs.filter(d => d).map(async d => {
- const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
- const data = d[annos ? fieldKey + "-annotations" : fieldKey];
- const docs = await DocListCastAsync(data);
- docs && newarray.push(...docs);
- func(depth, d);
- }));
+ await Promise.all(
+ docs
+ .filter(d => d)
+ .map(async d => {
+ const fieldKey = Doc.LayoutFieldKey(d);
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const data = d[annos ? fieldKey + '-annotations' : fieldKey];
+ const docs = await DocListCastAsync(data);
+ docs && newarray.push(...docs);
+ func(depth, d);
+ })
+ );
docs = newarray;
depth++;
}
@@ -174,11 +182,10 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* right side of each search result.
*/
static formatType(type: String): String {
- if (type === "pdf") {
- return "PDF";
- }
- else if (type === "image") {
- return "Img";
+ if (type === 'pdf') {
+ return 'PDF';
+ } else if (type === 'image') {
+ return 'Img';
}
return type.charAt(0).toUpperCase() + type.substring(1, 3);
@@ -195,9 +202,40 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
@action
searchCollection(query: string) {
const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
- const blockedKeys = ["x", "y", "proto", "width", "autoHeight", "acl-Override", "acl-Public", "context", "zIndex", "height", "text-scrollHeight", "text-height", "cloneFieldFilter", "isPrototype", "text-annotations",
- "dragFactory-count", "text-noTemplate", "aliases", "system", "layoutKey", "baseProto", "xMargin", "yMargin", "links", "layout", "layout_keyValue", "fitWidth", "viewType", "title-custom",
- "panX", "panY", "viewScale"];
+ const blockedKeys = [
+ 'x',
+ 'y',
+ 'proto',
+ 'width',
+ 'autoHeight',
+ 'acl-Override',
+ 'acl-Public',
+ 'context',
+ 'zIndex',
+ 'height',
+ 'text-scrollHeight',
+ 'text-height',
+ 'cloneFieldFilter',
+ 'isPrototype',
+ 'text-annotations',
+ 'dragFactory-count',
+ 'text-noTemplate',
+ 'aliases',
+ 'system',
+ 'layoutKey',
+ 'baseProto',
+ 'xMargin',
+ 'yMargin',
+ 'links',
+ 'layout',
+ 'layout_keyValue',
+ 'fitWidth',
+ 'viewType',
+ 'title-custom',
+ 'panX',
+ 'panY',
+ 'viewScale',
+ ];
const collection = CollectionDockingView.Instance;
query = query.toLowerCase();
@@ -208,10 +246,15 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
const docs = DocListCast(collection.rootDoc[Doc.LayoutFieldKey(collection.rootDoc)]);
const docIDs: String[] = [];
SearchBox.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
- const dtype = StrCast(doc.type, "string") as DocumentType;
+ const dtype = StrCast(doc.type, 'string') as DocumentType;
if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth > 0) {
const hlights = new Set<string>();
- SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key));
+ SearchBox.documentKeys(doc).forEach(
+ key =>
+ Field.toString(doc[key] as Field)
+ .toLowerCase()
+ .includes(query) && hlights.add(key)
+ );
blockedKeys.forEach(key => hlights.delete(key));
if (Array.from(hlights.keys()).length > 0) {
@@ -247,18 +290,16 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
this._results.forEach((_, linkedDoc) => {
this._linkedDocsIn.get(linkedDoc)?.add(doc);
});
- }
- else {
+ } else {
const linkedDocSet = new Set<Doc>();
- Doc.GetProto(doc)[DirectLinksSym].forEach((link) => {
+ Doc.GetProto(doc)[DirectLinksSym].forEach(link => {
const d1 = link?.anchor1 as Doc;
const d2 = link?.anchor2 as Doc;
if (doc === d1 && this._results.has(d2)) {
linkedDocSet.add(d2);
this._linkedDocsIn.get(d2)?.add(doc);
- }
- else if (doc === d2 && this._results.has(d1)) {
+ } else if (doc === d2 && this._results.has(d1)) {
linkedDocSet.add(d1);
this._linkedDocsIn.get(d1)?.add(doc);
}
@@ -286,8 +327,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
this._results.forEach((_, doc) => {
let nextPageRank = pageRankFromAll;
- this._linkedDocsIn.get(doc)?.forEach((linkedDoc) => {
- nextPageRank += DAMPENING_FACTOR * (this._pageRanks.get(linkedDoc) ?? 0) / (this._linkedDocsOut.get(linkedDoc)?.size ?? 1);
+ this._linkedDocsIn.get(doc)?.forEach(linkedDoc => {
+ nextPageRank += (DAMPENING_FACTOR * (this._pageRanks.get(linkedDoc) ?? 0)) / (this._linkedDocsOut.get(linkedDoc)?.size ?? 1);
});
nextPageRanks.set(doc, nextPageRank);
@@ -325,7 +366,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
*/
static documentKeys(doc: Doc) {
const keys: { [key: string]: boolean } = {};
- Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false));
+ Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)));
return Array.from(Object.keys(keys));
}
@@ -339,12 +380,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
const query = StrCast(this._searchString);
Doc.SetSearchQuery(query);
+ Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true));
this._results.clear();
if (query) {
this.searchCollection(query);
}
- }
+ };
/**
* This method resets the search by iterating through each result and removing all
@@ -365,8 +407,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* or opening it in a new tab.
*/
selectElement = async (doc: Doc, finishFunc: () => void) => {
- await DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, undefined, undefined, finishFunc);
- }
+ await DocumentManager.Instance.jumpToDocument(doc, true, undefined, [], undefined, undefined, undefined, finishFunc);
+ };
/**
* This method returns a JSX list of the options in the select drop-down menu, which
@@ -374,9 +416,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
*/
@computed
public get selectOptions() {
- const selectValues = ["all", "rtf", "image", "pdf", "web", "video", "audio", "collection"];
+ const selectValues = ['all', 'rtf', 'image', 'pdf', 'web', 'video', 'audio', 'collection'];
- return selectValues.map(value => <option key={value} value={value}>{SearchBox.formatType(value)}</option>);
+ return selectValues.map(value => (
+ <option key={value} value={value}>
+ {SearchBox.formatType(value)}
+ </option>
+ ));
}
/**
@@ -387,39 +433,37 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
const isLinkSearch: boolean = this.props.linkSearch;
- const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
+ const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
const resultsJSX = Array();
- sortedResults.forEach((result) => {
- var className = "searchBox-results-scroll-view-result";
+ sortedResults.forEach(result => {
+ var className = 'searchBox-results-scroll-view-result';
if (this._selectedResult === result[0]) {
- className += " searchBox-results-scroll-view-result-selected";
+ className += ' searchBox-results-scroll-view-result-selected';
}
const formattedType = SearchBox.formatType(StrCast(result[0].type));
const title = result[0].title;
- if (this._docTypeString === "all" || this._docTypeString === result[0].type) {
+ if (this._docTypeString === 'all' || this._docTypeString === result[0].type) {
validResults++;
resultsJSX.push(
- <Tooltip key={result[0][Id]} placement={"right"} title={<><div className="dash-tooltip">{title}</div></>}>
- <div onClick={isLinkSearch ?
- () => this.makeLink(result[0]) :
- e => {
- this.onResultClick(result[0]);
- e.stopPropagation();
- }} className={className}>
- <div className="searchBox-result-title">
- {title}
- </div>
- <div className="searchBox-result-type">
- {formattedType}
- </div>
- <div className="searchBox-result-keys">
- {result[1].join(", ")}
- </div>
+ <Tooltip key={result[0][Id]} placement={'right'} title={<div className="dash-tooltip">{title as string}</div>}>
+ <div
+ onClick={
+ isLinkSearch
+ ? () => this.makeLink(result[0])
+ : e => {
+ this.onResultClick(result[0]);
+ e.stopPropagation();
+ }
+ }
+ className={className}>
+ <div className="searchBox-result-title">{title as string}</div>
+ <div className="searchBox-result-type">{formattedType}</div>
+ <div className="searchBox-result-keys">{result[1].join(', ')}</div>
</div>
</Tooltip>
);
@@ -427,22 +471,31 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
});
return (
- <div style={{ pointerEvents: "all" }} className="searchBox-container">
- <div className="searchBox-bar" >
- {isLinkSearch ? (null) : <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
- {this.selectOptions}
- </select>}
- <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} onKeyPress={e => e.key === "Enter" ? this.submitSearch() : null} type="text" placeholder="Search..." id="search-input" className="searchBox-input" style={{ width: isLinkSearch ? "100%" : undefined, borderRadius: isLinkSearch ? "5px" : undefined }} ref={this._inputRef} />
- </div >
+ <div style={{ pointerEvents: 'all' }} className="searchBox-container">
+ <div className="searchBox-bar">
+ {isLinkSearch ? null : (
+ <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
+ {this.selectOptions}
+ </select>
+ )}
+ <input
+ defaultValue={''}
+ autoComplete="off"
+ onChange={this.onInputChange}
+ onKeyPress={e => (e.key === 'Enter' ? this.submitSearch() : null)}
+ type="text"
+ placeholder="Search..."
+ id="search-input"
+ className="searchBox-input"
+ style={{ width: isLinkSearch ? '100%' : undefined, borderRadius: isLinkSearch ? '5px' : undefined }}
+ ref={this._inputRef}
+ />
+ </div>
<div className="searchBox-results-container">
- <div className="searchBox-results-count">
- {`${validResults}` + " result" + (validResults === 1 ? "" : "s")}
- </div>
- <div className="searchBox-results-scroll-view">
- {resultsJSX}
- </div>
+ <div className="searchBox-results-count">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ <div className="searchBox-results-scroll-view">{resultsJSX}</div>
</div>
- </div >
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 923f1892e..d415e9367 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -1,29 +1,45 @@
@import "../global/globalCssVariables";
+
.topbar-container {
- display: flex;
- flex-direction: column;
- width: 100%;
- position: relative;
- font-size: 10px;
- line-height: 1;
- overflow-y: auto;
- overflow-x: visible;
- background: $dark-gray;
- overflow: visible;
- z-index: 1000;
-
- .topbar-bar {
- height: $topbar-height;
- display: grid;
- grid-auto-columns: 33.3% 33.3% 33.3%;
- align-items: center;
- background-color: $dark-gray;
-
- .topBar-icon {
+ flex-direction: column;
+ font-size: 10px;
+ line-height: 1;
+ overflow-y: auto;
+ overflow-x: visible;
+ background: $dark-gray;
+ overflow: visible;
+ z-index: 1000;
+ align-items: center;
+ height: $topbar-height;
+ background-color: $dark-gray;
+ cursor: default;
+
+ .topbar-inner-container {
+ display: flex;
+ flex-direction: row;
+ position: relative;
+ display: grid;
+ grid-auto-columns: 33.3% 33.3% 33.3%;
+ align-items: center;
+
+ // &:first-child {
+ // height: 20px;
+ // }
+ }
+
+ .topbar-button-text {
+ color: $white;
+ padding: 10px;
+ size: 15;
+
+ &:hover {
+ font-weight: 500;
+ }
+ }
+
+ .topbar-button-icon {
cursor: pointer;
- font-size: 12.5px;
- font-family: 'Roboto';
width: fit-content;
display: flex;
justify-content: center;
@@ -31,21 +47,23 @@
align-items: center;
justify-self: center;
align-self: center;
- border-radius: $standard-border-radius;
padding: 5px;
transition: linear 0.2s;
- color: $black;
- background-color: $light-gray;
+ color: $white;
&:hover {
- background-color: darken($color: $light-gray, $amount: 20);
+ background-color: darken($color: $light-gray, $amount: 20);
+ font-weight: 500;
}
- }
+ }
+ .topbar-title {
+ color: $white;
+ font-size: 17;
+ font-weight: 500;
+ }
-
-
- .topbar-center {
+ .topbar-center {
grid-column: 2;
display: inline-flex;
justify-content: center;
@@ -53,42 +71,23 @@
gap: 5px;
.topbar-dashboards {
- display: flex;
- flex-direction: row;
- gap: 5px;
- }
-
- .topbar-lozenge-dashboard {
- display: flex;
-
-
-
- .topbar-dashSelect {
- border: none;
- background-color: $dark-gray;
- color: $white;
- font-family: 'Roboto';
- font-size: 17;
- font-weight: 500;
-
- &:hover {
- cursor: pointer;
- }
- }
+ display: flex;
+ flex-direction: row;
+ gap: 5px;
}
- }
+ }
- .topbar-right {
+ .topbar-right {
grid-column: 3;
position: relative;
display: flex;
justify-content: flex-end;
gap: 5px;
margin-right: 5px;
- }
+ }
- .topbar-left {
+ .topbar-left {
grid-column: 1;
color: black;
font-family: 'Roboto';
@@ -98,123 +97,122 @@
gap: 5px;
.topBar-icon:hover {
- background-color: $close-red;
+ background-color: $close-red;
}
.topbar-lozenge-user,
.topbar-lozenge {
- height: 23;
- font-size: 12;
- color: white;
- font-family: 'Roboto';
- font-weight: 400;
- padding: 4px;
- align-self: center;
- margin-left: 7px;
- display: flex;
- align-items: center;
-
- .topbar-dashSelect {
- border: none;
- background-color: transparent;
- color: black;
- font-family: 'Roboto';
- font-size: 17;
- font-weight: 500;
-
- &:hover {
- cursor: pointer;
- }
- }
+ height: 23;
+ font-size: 12;
+ color: white;
+ font-family: 'Roboto';
+ font-weight: 400;
+ padding: 4px;
+ align-self: center;
+ margin-left: 7px;
+ display: flex;
+ align-items: center;
+
+ .topbar-dashSelect {
+ border: none;
+ background-color: transparent;
+ color: black;
+ font-family: 'Roboto';
+ font-size: 17;
+ font-weight: 500;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
}
.topbar-logoff {
- border-radius: 3px;
- background: olivedrab;
- color: white;
- display: none;
- margin-left: 5px;
- padding: 1px 2px 1px 2px;
- cursor: pointer;
+ border-radius: 3px;
+ background: olivedrab;
+ color: white;
+ display: none;
+ margin-left: 5px;
+ padding: 1px 2px 1px 2px;
+ cursor: pointer;
}
.topbar-logoff {
- background: red;
+ background: red;
}
.topbar-lozenge-user:hover {
- .topbar-logoff {
- display: inline-block;
- }
+ .topbar-logoff {
+ display: inline-block;
+ }
}
- }
+ }
- .topbar-barChild {
+ .topbar-barChild {
&.topbar-collection {
- flex: 0 1 auto;
- margin-left: 2px;
- margin-right: 2px
+ flex: 0 1 auto;
+ margin-left: 2px;
+ margin-right: 2px
}
&.topbar-input {
- margin: 5px;
- border-radius: 20px;
- border: $dark-gray;
- display: block;
- width: 130px;
- -webkit-transition: width 0.4s;
- transition: width 0.4s;
- /* align-self: stretch; */
- outline: none;
-
- &:focus {
- width: 500px;
- outline: none;
- }
+ margin: 5px;
+ border-radius: 20px;
+ border: $dark-gray;
+ display: block;
+ width: 130px;
+ -webkit-transition: width 0.4s;
+ transition: width 0.4s;
+ /* align-self: stretch; */
+ outline: none;
+
+ &:focus {
+ width: 500px;
+ outline: none;
+ }
}
&.topbar-filter {
- align-self: stretch;
+ align-self: stretch;
- button {
- transform: none;
-
- &:hover {
+ button {
transform: none;
- }
- }
+
+ &:hover {
+ transform: none;
+ }
+ }
}
&.topbar-submit {
- margin-left: 2px;
- margin-right: 2px
+ margin-left: 2px;
+ margin-right: 2px
}
&.topbar-close {
- color: $white;
- max-height: $topbar-height;
+ color: $white;
+ max-height: $topbar-height;
}
- }
- }
+ }
}
.topbar-results {
- display: flex;
- flex-direction: column;
- top: 300px;
- display: flex;
- flex-direction: column;
- height: 100%;
- overflow: visible;
-
- .no-result {
- width: 500px;
- background: $light-gray;
- padding: 10px;
- height: 50px;
- text-transform: uppercase;
- text-align: left;
- font-weight: bold;
- }
+ display: flex;
+ flex-direction: column;
+ top: 300px;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: visible;
+
+ .no-result {
+ width: 500px;
+ background: $light-gray;
+ padding: 10px;
+ height: 50px;
+ text-transform: uppercase;
+ text-align: left;
+ font-weight: bold;
+ }
} \ No newline at end of file
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index d5254e315..3fc0a237e 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,67 +1,106 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { observer } from "mobx-react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action } from 'mobx';
+import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
+import { AclAdmin, Doc } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
+import { GetEffectiveAcl } from '../../../fields/util';
import { Utils } from '../../../Utils';
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { SettingsManager } from "../../util/SettingsManager";
-import { undoBatch } from "../../util/UndoManager";
-import { Borders, Colors } from "../global/globalEnums";
-import "./TopBar.scss";
+import { DocumentManager } from '../../util/DocumentManager';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
+import { SharingManager } from '../../util/SharingManager';
+import { UndoManager } from '../../util/UndoManager';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { ContextMenu } from '../ContextMenu';
+import { DashboardView } from '../DashboardView';
+import { Borders, Colors } from '../global/globalEnums';
+import { MainView } from '../MainView';
+import './TopBar.scss';
/**
* ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
- * and settings and help buttons. Future scope for this bar is to include the collaborators that are on the same Dashboard.
+ * and settings and help buttons. Future scope for this bar is to include the collaborators that are on the same Dashboard.
*/
@observer
export class TopBar extends React.Component {
+ navigateToHome = () => {
+ CollectionDockingView.Instance?.CaptureThumbnail()?.then(() => {
+ Doc.ActivePage = 'home';
+ DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
+ });
+ };
render() {
- const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+ const activeDashboard = Doc.ActiveDashboard;
return (
//TODO:glr Add support for light / dark mode
- <div style={{ pointerEvents: "all" }} className="topbar-container">
- <div className="topbar-bar" style={{ background: Colors.DARK_GRAY, borderBottom: Borders.STANDARD }}>
+ <div style={{ pointerEvents: 'all', background: Colors.DARK_GRAY, borderBottom: Borders.STANDARD }} className="topbar-container">
+ <div className="topbar-inner-container">
<div className="topbar-left">
- <div className="topbar-lozenge-user">
- {`${Doc.CurrentUserEmail}`}
- </div>
- <div className="topbar-icon" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
- {"Log out"}
- </div>
+ {activeDashboard ? (
+ <>
+ <div
+ className="topbar-button-icon"
+ onClick={e => {
+ ContextMenu.Instance.addItem({ description: 'Logout', event: () => window.location.assign(Utils.prepend('/logout')), icon: 'edit' });
+ ContextMenu.Instance.displayMenu(e.clientX + 5, e.clientY + 10);
+ }}>
+ {Doc.CurrentUserEmail}
+ </div>
+ <div className="topbar-button-icon" onClick={this.navigateToHome}>
+ <FontAwesomeIcon icon="home" />
+ </div>
+ </>
+ ) : null}
</div>
- <div className="topbar-center" >
- <div className="topbar-lozenge-dashboard">
- <select className="topbar-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
- value={myDashboards.indexOf(CurrentUserUtils.ActiveDashboard)}
- style={{ color: Colors.WHITE }}>
- {myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
- </select>
+ <div className="topbar-center">
+ <div className="topbar-title" onClick={() => activeDashboard && SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}>
+ {activeDashboard ? StrCast(activeDashboard.title) : 'Dash'}
</div>
- <div className="topbar-dashboards">
- <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}
- >
- {"New"}<FontAwesomeIcon icon="plus"></FontAwesomeIcon>
- </div>
- {Doc.UserDoc().noviceMode ? (null) : <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.snapshotDashboard(Doc.UserDoc()))}
- >
- {"Snapshot"}<FontAwesomeIcon icon="camera"></FontAwesomeIcon>
- </div>}
+ <div
+ className="topbar-button-icon"
+ onClick={e => {
+ const dashView = activeDashboard && DocumentManager.Instance.getDocumentView(activeDashboard);
+ ContextMenu.Instance.addItem({ description: 'Open Dashboard View', event: this.navigateToHome, icon: 'edit' });
+ ContextMenu.Instance.addItem({
+ description: 'Snapshot Dashboard',
+ event: async () => {
+ const batch = UndoManager.StartBatch('snapshot');
+ await DashboardView.snapshotDashboard();
+ batch.end();
+ },
+ icon: 'edit',
+ });
+ dashView?.showContextMenu(e.clientX + 20, e.clientY + 30);
+ }}>
+ <FontAwesomeIcon color="white" size="lg" icon="bars" />
</div>
+ <Tooltip title={<div className="dash-tooltip">Browsing mode for directly navigating to documents</div>} placement="bottom">
+ <div className="topbar-button-icon" style={{ background: MainView.Instance._exploreMode ? Colors.LIGHT_BLUE : undefined }} onClick={action(() => (MainView.Instance._exploreMode = !MainView.Instance._exploreMode))}>
+ <FontAwesomeIcon color={MainView.Instance._exploreMode ? 'red' : 'white'} icon="eye" size="lg" />
+ </div>
+ </Tooltip>
</div>
- <div className="topbar-right" >
- <div className="topbar-icon" onClick={() => window.open(
- "https://brown-dash.github.io/Dash-Documentation/", "_blank")}>
- {"Help"}<FontAwesomeIcon icon="question-circle"></FontAwesomeIcon>
+ <div className="topbar-right">
+ {Doc.ActiveDashboard ? (
+ <div
+ className="topbar-button-icon"
+ onClick={() => {
+ SharingManager.Instance.open(undefined, activeDashboard);
+ }}>
+ {GetEffectiveAcl(Doc.GetProto(Doc.ActiveDashboard)) === AclAdmin ? 'Share' : 'view original'}
+ </div>
+ ) : null}
+ <div className="topbar-button-icon" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')}>
+ <FontAwesomeIcon icon="question-circle" />
</div>
- <div className="topbar-icon" onClick={() => SettingsManager.Instance.open()}>
- {"Settings"}<FontAwesomeIcon icon="cog"></FontAwesomeIcon>
+ <div className="topbar-button-icon" onClick={() => SettingsManager.Instance.open()}>
+ <FontAwesomeIcon icon="cog" />
</div>
-
</div>
</div>
- </div >
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index e0d328c89..02e44a793 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -1,86 +1,84 @@
-import { faPhoneSlash, faSync } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
-import { InkTool } from "../../../fields/InkField";
-import "../../views/nodes/WebBox.scss";
-import { DocumentDecorations } from "../DocumentDecorations";
-import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView";
-import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import "./DashWebRTCVideo.scss";
-import { hangup, initialize, refreshVideos } from "./WebCamLogic";
-import React = require("react");
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { IconLookup } from "@fortawesome/fontawesome-svg-core";
-
+import { IconLookup } from '@fortawesome/fontawesome-svg-core';
+import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { InkTool } from '../../../fields/InkField';
+import '../../views/nodes/WebBox.scss';
+import { DocumentDecorations } from '../DocumentDecorations';
+import { CollectionFreeFormDocumentViewProps } from '../nodes/CollectionFreeFormDocumentView';
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import './DashWebRTCVideo.scss';
+import { hangup, initialize, refreshVideos } from './WebCamLogic';
+import React = require('react');
/**
* This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
*/
@observer
export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentViewProps & FieldViewProps> {
-
private roomText: HTMLInputElement | undefined;
@observable remoteVideoAdded: boolean = false;
@action
changeUILook = () => {
this.remoteVideoAdded = true;
- }
+ };
/**
- * Function that submits the title entered by user on enter press.
- */
+ * Function that submits the title entered by user on enter press.
+ */
private onEnterKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode === 13) {
const submittedTitle = this.roomText!.value;
- this.roomText!.value = "";
+ this.roomText!.value = '';
this.roomText!.blur();
initialize(submittedTitle, this.changeUILook);
}
- }
-
+ };
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DashWebRTCVideo, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(DashWebRTCVideo, fieldKey);
+ }
@action
onClickRefresh = () => {
refreshVideos();
- }
+ };
onClickHangUp = () => {
hangup();
- }
+ };
render() {
- const content =
- <div className="webcam-cont" style={{ width: "100%", height: "100%" }}>
+ const content = (
+ <div className="webcam-cont" style={{ width: '100%', height: '100%' }}>
<div className="webcam-header">DashWebRTC</div>
- <input id="roomName" type="text" placeholder="Enter room name" ref={(e) => this.roomText = e!} onKeyDown={this.onEnterKeyDown} />
+ <input id="roomName" type="text" placeholder="Enter room name" ref={e => (this.roomText = e!)} onKeyDown={this.onEnterKeyDown} />
<div className="videoContainer">
- <video id="localVideo" className={"RTCVideo" + (this.remoteVideoAdded ? " side" : " main")} autoPlay playsInline muted ref={(e) => {
- }}></video>
- <video id="remoteVideo" className="RTCVideo main" autoPlay playsInline ref={(e) => {
- }}></video>
+ <video id="localVideo" className={'RTCVideo' + (this.remoteVideoAdded ? ' side' : ' main')} autoPlay playsInline muted ref={e => {}}></video>
+ <video id="remoteVideo" className="RTCVideo main" autoPlay playsInline ref={e => {}}></video>
</div>
<div className="buttonContainer">
- <div className="videoButtons" style={{ background: "red" }} onClick={this.onClickHangUp}><FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" /></div>
- <div className="videoButtons" style={{ background: "green" }} onClick={this.onClickRefresh}><FontAwesomeIcon icon={faSync as IconLookup} color="white" /></div>
+ <div className="videoButtons" style={{ background: 'red' }} onClick={this.onClickHangUp}>
+ <FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" />
+ </div>
+ <div className="videoButtons" style={{ background: 'green' }} onClick={this.onClickRefresh}>
+ <FontAwesomeIcon icon={faSync as IconLookup} color="white" />
+ </div>
</div>
- </div >;
+ </div>
+ );
const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
- const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+ const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? '-interactive' : '');
return (
<>
- <div className={classname} >
- {content}
- </div>
- {!frozen ? (null) : <div className="webBox-overlay" />}
- </>);
+ <div className={classname}>{content}</div>
+ {!frozen ? null : <div className="webBox-overlay" />}
+ </>
+ );
}
-
-
-} \ No newline at end of file
+}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1253cf9c7..0c7504913 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,59 +1,54 @@
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { saveAs } from "file-saver";
-import { action, computed, observable, ObservableMap, runInAction } from "mobx";
-import { computedFn } from "mobx-utils";
-import { alias, map, serializable } from "serializr";
-import { DocServer } from "../client/DocServer";
-import { DocumentType } from "../client/documents/DocumentTypes";
-import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
-import { LinkManager } from "../client/util/LinkManager";
-import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { SelectionManager } from "../client/util/SelectionManager";
-import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
-import { UndoManager } from "../client/util/UndoManager";
-import { DashColor, intersectRect, Utils } from "../Utils";
-import { DateField } from "./DateField";
-import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
-import { List } from "./List";
-import { ObjectField } from "./ObjectField";
-import { PrefetchProxy, ProxyField } from "./Proxy";
-import { FieldId, RefField } from "./RefField";
-import { RichTextField } from "./RichTextField";
-import { listSpec } from "./Schema";
-import { ComputedField, ScriptField } from "./ScriptField";
-import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField";
-import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
-import JSZip = require("jszip");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { saveAs } from 'file-saver';
+import { action, computed, observable, ObservableMap, runInAction } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { alias, map, serializable } from 'serializr';
+import { DocServer } from '../client/DocServer';
+import { DocumentType } from '../client/documents/DocumentTypes';
+import { LinkManager } from '../client/util/LinkManager';
+import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { SelectionManager } from '../client/util/SelectionManager';
+import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
+import { UndoManager } from '../client/util/UndoManager';
+import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils';
+import { DateField } from './DateField';
+import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols';
+import { InkTool } from './InkField';
+import { List } from './List';
+import { ObjectField } from './ObjectField';
+import { PrefetchProxy, ProxyField } from './Proxy';
+import { FieldId, RefField } from './RefField';
+import { RichTextField } from './RichTextField';
+import { listSpec } from './Schema';
+import { ComputedField, ScriptField } from './ScriptField';
+import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
+import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from './URLField';
+import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util';
+import JSZip = require('jszip');
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
const onDelegate = Object.keys(doc).includes(key);
const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- return !Field.IsField(field) ? "" : (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
+ return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
}
export function toScriptString(field: Field): string {
- if (typeof field === "string") return `"${field}"`;
- if (typeof field === "number" || typeof field === "boolean") return String(field);
- if (field === undefined || field === null) return "null";
+ if (typeof field === 'string') return `"${field}"`;
+ if (typeof field === 'number' || typeof field === 'boolean') return String(field);
+ if (field === undefined || field === null) return 'null';
return field[ToScriptString]();
}
export function toString(field: Field): string {
- if (typeof field === "string") return field;
- if (typeof field === "number" || typeof field === "boolean") return String(field);
+ if (typeof field === 'string') return field;
+ if (typeof field === 'number' || typeof field === 'boolean') return String(field);
if (field instanceof ObjectField) return field[ToString]();
if (field instanceof RefField) return field[ToString]();
- return "";
+ return '';
}
export function IsField(field: any): field is Field;
export function IsField(field: any, includeUndefined: true): field is Field | undefined;
export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
- return (typeof field === "string")
- || (typeof field === "number")
- || (typeof field === "boolean")
- || (field instanceof ObjectField)
- || (field instanceof RefField)
- || (includeUndefined && field === undefined);
+ return typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean' || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
}
export function Copy(field: any) {
return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
@@ -77,40 +72,50 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
}
-export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> { return Cast(field, Doc); }
-
-export function NumListCast(field: FieldResult) { return Cast(field, listSpec("number"), []); }
-export function StrListCast(field: FieldResult) { return Cast(field, listSpec("string"), []); }
-export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; }
-export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; }
-
-export const WidthSym = Symbol("Width");
-export const HeightSym = Symbol("Height");
-export const DataSym = Symbol("Data");
-export const LayoutSym = Symbol("Layout");
-export const FieldsSym = Symbol("Fields");
-export const AclSym = Symbol("Acl");
-export const DirectLinksSym = Symbol("DirectLinks");
-export const AclUnset = Symbol("AclUnset");
-export const AclPrivate = Symbol("AclOwnerOnly");
-export const AclReadonly = Symbol("AclReadOnly");
-export const AclAugment = Symbol("AclAugment");
-export const AclSelfEdit = Symbol("AclSelfEdit");
-export const AclEdit = Symbol("AclEdit");
-export const AclAdmin = Symbol("AclAdmin");
-export const UpdatingFromServer = Symbol("UpdatingFromServer");
-export const Initializing = Symbol("Initializing");
-export const ForceServerWrite = Symbol("ForceServerWrite");
-export const CachedUpdates = Symbol("Cached updates");
+export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
+ return Cast(field, Doc);
+}
+
+export function NumListCast(field: FieldResult) {
+ return Cast(field, listSpec('number'), []);
+}
+export function StrListCast(field: FieldResult) {
+ return Cast(field, listSpec('string'), []);
+}
+export function DocListCast(field: FieldResult) {
+ return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
+}
+export function DocListCastOrNull(field: FieldResult) {
+ return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined;
+}
+
+export const WidthSym = Symbol('Width');
+export const HeightSym = Symbol('Height');
+export const DataSym = Symbol('Data');
+export const LayoutSym = Symbol('Layout');
+export const FieldsSym = Symbol('Fields');
+export const AclSym = Symbol('Acl');
+export const DirectLinksSym = Symbol('DirectLinks');
+export const AclUnset = Symbol('AclUnset');
+export const AclPrivate = Symbol('AclOwnerOnly');
+export const AclReadonly = Symbol('AclReadOnly');
+export const AclAugment = Symbol('AclAugment');
+export const AclSelfEdit = Symbol('AclSelfEdit');
+export const AclEdit = Symbol('AclEdit');
+export const AclAdmin = Symbol('AclAdmin');
+export const UpdatingFromServer = Symbol('UpdatingFromServer');
+export const Initializing = Symbol('Initializing');
+export const ForceServerWrite = Symbol('ForceServerWrite');
+export const CachedUpdates = Symbol('Cached updates');
const AclMap = new Map<string, symbol>([
- ["None", AclUnset],
+ ['None', AclUnset],
[SharingPermissions.None, AclPrivate],
[SharingPermissions.View, AclReadonly],
[SharingPermissions.Augment, AclAugment],
[SharingPermissions.SelfEdit, AclSelfEdit],
[SharingPermissions.Edit, AclEdit],
- [SharingPermissions.Admin, AclAdmin]
+ [SharingPermissions.Admin, AclAdmin],
]);
// caches the document access permissions for the current user.
@@ -120,7 +125,7 @@ export function updateCachedAcls(doc: Doc) {
const permissions: { [key: string]: symbol } = {};
doc[UpdatingFromServer] = true;
- Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
+ Object.keys(doc).filter(key => key.startsWith('acl') && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
doc[UpdatingFromServer] = false;
if (Object.keys(permissions).length) {
@@ -134,8 +139,97 @@ export function updateCachedAcls(doc: Doc) {
}
@scriptingGlobal
-@Deserializable("Doc", updateCachedAcls).withFields(["id"])
+@Deserializable('Doc', updateCachedAcls).withFields(['id'])
export class Doc extends RefField {
+ //TODO tfs: these should be temporary...
+ private static mainDocId: string | undefined;
+ public static get MainDocId() {
+ return this.mainDocId;
+ }
+ public static set MainDocId(id: string | undefined) {
+ this.mainDocId = id;
+ }
+ @observable public static GuestDashboard: Doc | undefined;
+ @observable public static GuestTarget: Doc | undefined;
+ @observable public static GuestMobile: Doc | undefined;
+ public static get MySharedDocs() {
+ return DocCast(Doc.UserDoc().mySharedDocs);
+ }
+ public static get MyUserDocView() {
+ return DocCast(Doc.UserDoc().myUserDocView);
+ }
+ public static get MyDockedBtns() {
+ return DocCast(Doc.UserDoc().myDockedBtns);
+ }
+ public static get MySearcher() {
+ return DocCast(Doc.UserDoc().mySearcher);
+ }
+ public static get MyHeaderBar() {
+ return DocCast(Doc.UserDoc().myHeaderBar);
+ }
+ public static get MyLeftSidebarMenu() {
+ return DocCast(Doc.UserDoc().myLeftSidebarMenu);
+ }
+ public static get MyLeftSidebarPanel() {
+ return DocCast(Doc.UserDoc().myLeftSidebarPanel);
+ }
+ public static get MyContextMenuBtns() {
+ return DocCast(Doc.UserDoc().myContextMenuBtns);
+ }
+ public static get MyRecentlyClosed() {
+ return DocCast(Doc.UserDoc().myRecentlyClosed);
+ }
+ public static get MyTrails() {
+ return DocCast(Doc.UserDoc().myTrails);
+ }
+ public static get MyOverlayDocs() {
+ return DocCast(Doc.UserDoc().myOverlayDocs);
+ }
+ public static get MyPublishedDocs() {
+ return DocCast(Doc.UserDoc().myPublishedDocs);
+ }
+ public static get MyDashboards() {
+ return DocCast(Doc.UserDoc().myDashboards);
+ }
+ public static get MyTemplates() {
+ return DocCast(Doc.UserDoc().myTemplates);
+ }
+ public static get MyImports() {
+ return DocCast(Doc.UserDoc().myImports);
+ }
+ public static get MyFilesystem() {
+ return DocCast(Doc.UserDoc().myFilesystem);
+ }
+ public static get MyFileOrphans() {
+ return DocCast(Doc.UserDoc().myFileOrphans);
+ }
+ public static get MyTools() {
+ return DocCast(Doc.UserDoc().myTools);
+ }
+ public static get ActivePage() {
+ return StrCast(Doc.UserDoc().activePage);
+ }
+ public static set ActivePage(val) {
+ Doc.UserDoc().activePage = val;
+ }
+ public static get ActiveDashboard() {
+ return DocCast(Doc.UserDoc().activeDashboard);
+ }
+ public static set ActiveDashboard(val: Doc | undefined) {
+ Doc.UserDoc().activeDashboard = val;
+ }
+ public static set ActiveTool(tool: InkTool) {
+ Doc.UserDoc().activeTool = tool;
+ }
+ public static get ActiveTool(): InkTool {
+ return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;
+ }
+ public static get ActivePresentation() {
+ return DocCast(Doc.UserDoc().activePresentation);
+ }
+ public static set ActivePresentation(val) {
+ Doc.UserDoc().activePresentation = val;
+ }
constructor(id?: FieldId, forceSave?: boolean) {
super(id);
const doc = new Proxy<this>(this, {
@@ -146,24 +240,26 @@ export class Doc extends RefField {
ownKeys: target => {
const obj = {} as any;
if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
- runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
+ runInAction(() => (obj.__LAYOUT__ = target.__LAYOUT__));
return Object.keys(obj);
},
getOwnPropertyDescriptor: (target, prop) => {
- if (prop.toString() === "__LAYOUT__") {
+ if (prop.toString() === '__LAYOUT__') {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
if (prop in target.__fieldKeys) {
return {
- configurable: true,//TODO Should configurable be true?
+ configurable: true, //TODO Should configurable be true?
enumerable: true,
- value: 0//() => target.__fields[prop])
+ value: 0, //() => target.__fields[prop])
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
},
deleteProperty: deleteProperty,
- defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+ defineProperty: () => {
+ throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
+ },
});
this[SelfProxy] = doc;
if (!id || forceSave) {
@@ -175,24 +271,30 @@ export class Doc extends RefField {
proto: Opt<Doc>;
[key: string]: FieldResult;
- @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
- private get __fields() { return this.___fields; }
+ @serializable(alias('fields', map(autoObject(), { afterDeserialize: afterDocDeserialize })))
+ private get __fields() {
+ return this.___fields;
+ }
private set __fields(value) {
this.___fields = value;
for (const key in value) {
const field = value[key];
- (field !== undefined) && (this.__fieldKeys[key] = true);
+ field !== undefined && (this.__fieldKeys[key] = true);
if (!(field instanceof ObjectField)) continue;
field[Parent] = this[Self];
field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
}
}
- private get __fieldKeys() { return this.___fieldKeys; }
- private set __fieldKeys(value) { this.___fieldKeys = value; }
+ private get __fieldKeys() {
+ return this.___fieldKeys;
+ }
+ private set __fieldKeys(value) {
+ this.___fieldKeys = value;
+ }
@observable private ___fields: any = {};
@observable private ___fieldKeys: any = {};
- @observable public [AclSym]: { [key: string]: symbol };
+ @observable public [AclSym]: { [key: string]: symbol } = {};
@observable public [DirectLinksSym]: Set<Doc> = new Set();
private [UpdatingFromServer]: boolean = false;
@@ -201,7 +303,7 @@ export class Doc extends RefField {
private [Update] = (diff: any) => {
(!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff);
- }
+ };
private [Self] = this;
private [SelfProxy]: any;
@@ -209,38 +311,52 @@ export class Doc extends RefField {
public [WidthSym] = () => NumCast(this[SelfProxy]._width);
public [HeightSym] = () => NumCast(this[SelfProxy]._height);
public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
- public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? "-inaccessible-" : this[SelfProxy].title})`;
- public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
+ public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
+ public get [LayoutSym]() {
+ return this[SelfProxy].__LAYOUT__;
+ }
public get [DataSym]() {
const self = this[SelfProxy];
- return self.resolvedDataDoc && !self.isTemplateForField ? self :
- Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+ return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
}
@computed get __LAYOUT__(): Doc | undefined {
const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
if (templateLayoutDoc) {
let renderFieldKey: any;
- const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")];
- if (typeof layoutField === "string") {
- renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1];
+ const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, 'layout')];
+ if (typeof layoutField === 'string') {
+ renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1];
} else {
return Cast(layoutField, Doc, null);
}
- return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc;
+ return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
}
return undefined;
-
}
private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
- public static CurrentUserEmail: string = "";
- public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); }
+ public static get noviceMode() {
+ return Doc.UserDoc().noviceMode as boolean;
+ }
+ public static set noviceMode(val) {
+ Doc.UserDoc().noviceMode = val;
+ }
+ public static get defaultAclPrivate() {
+ return Doc.UserDoc().defaultAclPrivate;
+ }
+ public static set defaultAclPrivate(val) {
+ Doc.UserDoc().defaultAclPrivate = val;
+ }
+ public static CurrentUserEmail: string = '';
+ public static get CurrentUserEmailNormalized() {
+ return normalizeEmail(Doc.CurrentUserEmail);
+ }
public async [HandleUpdate](diff: any) {
const set = diff.$set;
const sameAuthor = this.author === Doc.CurrentUserEmail;
if (set) {
for (const key in set) {
- const fprefix = "fields.";
+ const fprefix = 'fields.';
if (!key.startsWith(fprefix)) {
continue;
}
@@ -251,7 +367,7 @@ export class Doc extends RefField {
this[UpdatingFromServer] = true;
this[fKey] = value;
this[UpdatingFromServer] = false;
- if (fKey.startsWith("acl")) {
+ if (fKey.startsWith('acl')) {
updateCachedAcls(this);
}
if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
@@ -259,7 +375,7 @@ export class Doc extends RefField {
}
};
const writeMode = DocServer.getFieldWriteMode(fKey);
- if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) {
+ if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
await fn();
} else {
@@ -270,7 +386,7 @@ export class Doc extends RefField {
const unset = diff.$unset;
if (unset) {
for (const key in unset) {
- if (!key.startsWith("fields.")) {
+ if (!key.startsWith('fields.')) {
continue;
}
const fKey = key.substring(7);
@@ -322,7 +438,7 @@ export namespace Doc {
return {
end() {
makeEditable();
- }
+ },
};
}
@@ -337,15 +453,16 @@ export namespace Doc {
return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
}
export function IsPrototype(doc: Doc) {
- return GetT(doc, "isPrototype", "boolean", true);
+ return GetT(doc, 'isPrototype', 'boolean', true);
}
export function IsBaseProto(doc: Doc) {
- return GetT(doc, "baseProto", "boolean", true);
+ return GetT(doc, 'baseProto', 'boolean', true);
}
export function IsSystem(doc: Doc) {
- return GetT(doc, "system", "boolean", true);
+ return GetT(doc, 'system', 'boolean', true);
}
export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+ if (key.startsWith('_')) key = key.substring(1);
const hasProto = doc.proto instanceof Doc;
const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
@@ -354,7 +471,7 @@ export namespace Doc {
} else doc.proto![key] = value;
}
export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
- const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
+ const proto = Object.getOwnPropertyNames(doc).indexOf('isPrototype') === -1 ? doc.proto : doc;
if (proto) {
proto[key] = value;
@@ -386,7 +503,8 @@ export namespace Doc {
for (const key in fields) {
if (fields.hasOwnProperty(key)) {
const value = fields[key];
- if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds?
+ if (!skipUndefineds || value !== undefined) {
+ // Do we want to filter out undefineds?
doc[key] = value;
}
}
@@ -398,10 +516,10 @@ export namespace Doc {
// compare whether documents or their protos match
export function AreProtosEqual(doc?: Doc, other?: Doc) {
if (!doc || !other) return false;
- const r = (doc === other);
- const r2 = (Doc.GetProto(doc) === other);
- const r3 = (Doc.GetProto(other) === doc);
- const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined);
+ const r = doc === other;
+ const r2 = Doc.GetProto(doc) === other;
+ const r3 = Doc.GetProto(other) === doc;
+ const r4 = Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined;
return r || r2 || r3 || r4;
}
@@ -412,7 +530,7 @@ export namespace Doc {
if (doc instanceof Promise) {
// console.log("GetProto: warning: got Promise insead of Doc");
}
- const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
+ const proto = doc && (Doc.GetT(doc, 'isPrototype', 'boolean', true) ? doc : doc.proto || doc);
return proto === doc ? proto : Doc.GetProto(proto);
}
export function GetDataDoc(doc: Doc): Doc {
@@ -421,7 +539,7 @@ export namespace Doc {
}
export function allKeys(doc: Doc): string[] {
- const results: Set<string> = new Set;
+ const results: Set<string> = new Set();
let proto: Doc | undefined = doc;
while (proto) {
@@ -436,8 +554,8 @@ export namespace Doc {
* @returns the index of doc toFind in list of docs, -1 otherwise
*/
export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
- let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
- index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
+ let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind ? i : p), -1);
+ index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind) ? i : p), -1);
return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
}
@@ -473,7 +591,7 @@ export namespace Doc {
const list = Cast(listDoc[key], listSpec(Doc));
if (list) {
if (allowDuplicates !== true) {
- const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1);
+ const pind = list.reduce((l, d, i) => (d instanceof Doc && d[Id] === doc[Id] ? i : l), -1);
if (pind !== -1) {
return true;
//list.splice(pind, 1); // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
@@ -481,15 +599,13 @@ export namespace Doc {
}
if (first) {
list.splice(0, 0, doc);
- }
- else {
+ } else {
const ind = relativeTo ? list.indexOf(relativeTo) : -1;
if (ind === -1) {
if (reversed) list.splice(0, 0, doc);
else list.push(doc);
- }
- else {
- if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc);
+ } else {
+ if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc);
else list.splice(before ? ind : ind + 1, 0, doc);
}
}
@@ -502,19 +618,24 @@ export namespace Doc {
* Computes the bounds of the contents of a set of documents.
*/
export function ComputeContentBounds(docList: Doc[]) {
- const bounds = docList.reduce((bounds, doc) => {
- const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
- const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
- return {
- x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
- r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
- };
- }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
+ const bounds = docList.reduce(
+ (bounds, doc) => {
+ const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
+ const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
+ return {
+ x: Math.min(sptX, bounds.x),
+ y: Math.min(sptY, bounds.y),
+ r: Math.max(bptX, bounds.r),
+ b: Math.max(bptY, bounds.b),
+ };
+ },
+ { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }
+ );
return bounds;
}
export function MakeAlias(doc: Doc, id?: string) {
- const alias = !GetT(doc, "isPrototype", "boolean", true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
+ const alias = !GetT(doc, 'isPrototype', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
const layout = Doc.LayoutField(alias);
if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) {
Doc.SetLayout(alias, Doc.MakeAlias(layout));
@@ -524,71 +645,73 @@ export namespace Doc {
alias.title = ComputedField.MakeFunction(`renameAlias(this)`);
alias.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(Doc.GetProto(doc)[DataSym], "aliases", alias);
+ Doc.AddDocToList(Doc.GetProto(doc)[DataSym], 'aliases', alias);
return alias;
}
- export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+ export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
if (Doc.IsBaseProto(doc)) return doc;
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
- const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
+ const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
- const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
- const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
- await Promise.all(Object.keys(doc).map(async key => {
- if (filter.includes(key)) return;
- const assignKey = (val: any) => !dontCreate && (copy[key] = val);
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = async (field: ObjectField) => {
- const list = await Cast(doc[key], listSpec(Doc));
- const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
- if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
- !dontCreate && assignKey(new List<Doc>(clones));
- } else if (doc[key] instanceof Doc) {
- assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
- } else {
- !dontCreate && assignKey(ObjectField.MakeCopy(field));
- if (field instanceof RichTextField) {
- if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
- rtfs.push({ copy, key, field });
+ const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions;
+ const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])];
+ await Promise.all(
+ Object.keys(doc).map(async key => {
+ if (filter.includes(key)) return;
+ const assignKey = (val: any) => !dontCreate && (copy[key] = val);
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const field = ProxyField.WithoutProxy(() => doc[key]);
+ const copyObjectField = async (field: ObjectField) => {
+ const list = await Cast(doc[key], listSpec(Doc));
+ const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
+ if (docs !== undefined && docs.length) {
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
+ !dontCreate && assignKey(new List<Doc>(clones));
+ } else if (doc[key] instanceof Doc) {
+ assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
+ } else {
+ !dontCreate && assignKey(ObjectField.MakeCopy(field));
+ if (field instanceof RichTextField) {
+ if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
+ rtfs.push({ copy, key, field });
+ }
}
}
- }
- };
- if (key === "proto") {
- if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
- }
- } else if (key === "anchor1" || key === "anchor2") {
- if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
- }
- } else {
- if (field instanceof RefField) {
- assignKey(field);
- } else if (cfield instanceof ComputedField) {
- !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
- } else if (field instanceof ObjectField) {
- await copyObjectField(field);
- } else if (field instanceof Promise) {
- debugger; //This shouldn't happen...
+ };
+ if (key === 'proto') {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
+ }
+ } else if (key === 'anchor1' || key === 'anchor2') {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
+ }
} else {
- assignKey(field);
+ if (field instanceof RefField) {
+ assignKey(field);
+ } else if (cfield instanceof ComputedField) {
+ !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
+ } else if (field instanceof ObjectField) {
+ await copyObjectField(field);
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happen...
+ } else {
+ assignKey(field);
+ }
}
- }
- }));
+ })
+ );
for (const link of Array.from(doc[DirectLinksSym])) {
const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
linkMap.set(link, linkClone);
}
if (!dontCreate) {
- Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
+ Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true);
asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
if (!Doc.IsPrototype(copy)) {
- Doc.AddDocToList(doc, "branches", Doc.GetProto(copy));
+ Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy));
}
cloneMap.set(doc[Id], copy);
}
@@ -596,20 +719,20 @@ export namespace Doc {
}
export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<Doc, Doc>();
- const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch);
Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
- return attr + "\"" + (mapped ? mapped[Id] : id) + "\"";
+ return attr + '"' + (mapped ? mapped[Id] : id) + '"';
};
const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
return href + (mapped ? mapped[Id] : id);
};
const regex = `(${Doc.localServerPath()})([^"]*)`;
- const re = new RegExp(regex, "g");
+ const re = new RegExp(regex, 'g');
copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
});
return { clone: copy, map: cloneMap };
@@ -623,37 +746,36 @@ export namespace Doc {
// a.click();
const { clone, map } = await Doc.MakeClone(doc, true);
function replacer(key: any, value: any) {
- if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined;
+ if (['branchOf', 'cloneOf', 'context', 'cursors'].includes(key)) return undefined;
else if (value instanceof Doc) {
- if (key !== "field" && Number.isNaN(Number(key))) {
+ if (key !== 'field' && Number.isNaN(Number(key))) {
const __fields = value[FieldsSym]();
- return { id: value[Id], __type: "Doc", fields: __fields };
+ return { id: value[Id], __type: 'Doc', fields: __fields };
} else {
- return { fieldId: value[Id], __type: "proxy" };
+ return { fieldId: value[Id], __type: 'proxy' };
}
- }
- else if (value instanceof ScriptField) return { script: value.script, __type: "script" };
- else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" };
- else if (value instanceof ImageField) return { url: value.url.href, __type: "image" };
- else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" };
- else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" };
- else if (value instanceof VideoField) return { url: value.url.href, __type: "video" };
- else if (value instanceof WebField) return { url: value.url.href, __type: "web" };
- else if (value instanceof MapField) return { url: value.url.href, __type: "map" };
- else if (value instanceof DateField) return { date: value.toString(), __type: "date" };
- else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" };
- else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" };
- else if (value instanceof ComputedField) return { script: value.script, __type: "computed" };
+ } else if (value instanceof ScriptField) return { script: value.script, __type: 'script' };
+ else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: 'RichTextField' };
+ else if (value instanceof ImageField) return { url: value.url.href, __type: 'image' };
+ else if (value instanceof PdfField) return { url: value.url.href, __type: 'pdf' };
+ else if (value instanceof AudioField) return { url: value.url.href, __type: 'audio' };
+ else if (value instanceof VideoField) return { url: value.url.href, __type: 'video' };
+ else if (value instanceof WebField) return { url: value.url.href, __type: 'web' };
+ else if (value instanceof MapField) return { url: value.url.href, __type: 'map' };
+ else if (value instanceof DateField) return { date: value.toString(), __type: 'date' };
+ else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: 'proxy' };
+ else if (value instanceof Array && key !== 'fields') return { fields: value, __type: 'list' };
+ else if (value instanceof ComputedField) return { script: value.script, __type: 'computed' };
else return value;
}
const docs: { [id: string]: any } = {};
- Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]);
+ Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
const docString = JSON.stringify({ id: doc[Id], docs }, replacer);
const zip = new JSZip();
- zip.file(doc.title + ".json", docString);
+ zip.file('doc.json', docString);
// // Generate a directory within the Zip file structure
// var img = zip.folder("images");
@@ -662,11 +784,10 @@ export namespace Doc {
// img.file("smile.gif", imgData, {base64: true});
// Generate the zip file asynchronously
- zip.generateAsync({ type: "blob" })
- .then((content: any) => {
- // Force down of the Zip file
- saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title?
- });
+ zip.generateAsync({ type: 'blob' }).then((content: any) => {
+ // Force down of the Zip file
+ saveAs(content, doc.title + '.zip'); // glr: Possibly change the name of the document to match the title?
+ });
}
//
// Determines whether the layout needs to be expanded (as a template).
@@ -689,46 +810,47 @@ export namespace Doc {
// layout_mytemplate(somparam=somearg).
// then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
- const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS);
- if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc;
+ const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || StrCast(templateLayoutDoc.PARAMS);
+ if ((!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc)) || !targetDoc) return templateLayoutDoc;
- const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
+ const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
// First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
// using the template layout doc's id as the field key.
// If it doesn't find the expanded layout, then it makes a delegate of the template layout and
// saves it on the data doc indexed by the template layout's id.
//
- const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS";
+ const params = args.split('=').length > 1 ? args.split('=')[0] : 'PARAMS';
const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
- const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]";
+ const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']';
let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
expandedTemplateLayout = undefined;
_pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
- }
- else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
+ } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
if (!targetDoc[expandedLayoutFieldKey]) {
_pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true);
- setTimeout(action(() => {
- const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]");
- // the template's arguments are stored in params which is derefenced to find
- // the actual field key where the parameterized template data is stored.
- newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances
- newLayoutDoc.rootDocument = targetDoc;
- const dataDoc = Doc.GetProto(targetDoc);
- newLayoutDoc.resolvedDataDoc = dataDoc;
- if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
- dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
- }
- targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
-
- _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args);
- }));
+ setTimeout(
+ action(() => {
+ const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, '[' + templateLayoutDoc.title + ']');
+ // the template's arguments are stored in params which is derefenced to find
+ // the actual field key where the parameterized template data is stored.
+ newLayoutDoc[params] = args !== '...' ? args : ''; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances
+ newLayoutDoc.rootDocument = targetDoc;
+ const dataDoc = Doc.GetProto(targetDoc);
+ newLayoutDoc.resolvedDataDoc = dataDoc;
+ if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
+ dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
+ }
+ targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
+
+ _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args);
+ })
+ );
}
}
}
@@ -739,17 +861,17 @@ export namespace Doc {
// otherwise, it just returns the childDoc
export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) {
if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) {
- console.log("No, no, no!");
+ console.log('No, no, no!');
return { layout: childDoc, data: childDoc };
}
- const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc);
- return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc };
+ const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc;
+ return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc };
}
export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
Object.keys(doc).forEach(key => {
const field = ProxyField.WithoutProxy(() => doc[key]);
- if (key === "proto" && copyProto) {
+ if (key === 'proto' && copyProto) {
if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto);
}
@@ -769,14 +891,14 @@ export namespace Doc {
return overwrite;
}
- export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
+ export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
const copy = new Doc(copyProtoId, true);
- const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []);
+ const exclude = Cast(doc.cloneFieldFilter, listSpec('string'), []);
Object.keys(doc).forEach(key => {
if (exclude.includes(key)) return;
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
- if (key === "proto" && copyProto) {
+ if (key === 'proto' && copyProto) {
if (doc[key] instanceof Doc) {
copy[key] = Doc.MakeCopy(doc[key]!, false);
}
@@ -784,11 +906,14 @@ export namespace Doc {
if (field instanceof RefField) {
copy[key] = field;
} else if (cfield instanceof ComputedField) {
- copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript);
+ copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript);
} else if (field instanceof ObjectField) {
- copy[key] = doc[key] instanceof Doc ?
- key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields
- ObjectField.MakeCopy(field);
+ copy[key] =
+ doc[key] instanceof Doc
+ ? key.includes('layout[')
+ ? undefined
+ : doc[key] // reference documents except remove documents that are expanded teplate fields
+ : ObjectField.MakeCopy(field);
} else if (field instanceof Promise) {
debugger; //This shouldn't happend...
} else {
@@ -801,14 +926,16 @@ export namespace Doc {
Doc.GetProto(copy).context = undefined;
Doc.GetProto(copy).aliases = new List<Doc>([copy]);
} else {
- Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy);
+ Doc.AddDocToList(Doc.GetProto(copy)[DataSym], 'aliases', copy);
}
copy.context = undefined;
- Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
+ Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared');
+ if (retitle) {
+ copy.title = incrementTitleCopy(StrCast(copy.title));
+ }
return copy;
}
-
export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
@@ -817,7 +944,10 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = doc;
delegate.author = Doc.CurrentUserEmail;
- if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate);
+ Object.keys(doc)
+ .filter(key => key.startsWith('acl'))
+ .forEach(key => (delegate[key] = doc[key]));
+ if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], 'aliases', delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
return delegate;
@@ -840,7 +970,7 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = delegateProto;
delegate.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(delegateProto[DataSym], "aliases", delegate);
+ Doc.AddDocToList(delegateProto[DataSym], 'aliases', delegate);
delegate[Initializing] = false;
delegateProto[Initializing] = false;
return delegate;
@@ -852,11 +982,11 @@ export namespace Doc {
const proto = new Doc();
proto.author = Doc.CurrentUserEmail;
const target = Doc.MakeDelegate(proto);
- const targetKey = StrCast(templateDoc.layoutKey, "layout");
- const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
+ const targetKey = StrCast(templateDoc.layoutKey, 'layout');
+ const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
target.layoutKey = targetKey;
applied && (Doc.GetProto(applied).type = templateDoc.type);
- Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared");
+ Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared');
return applied;
}
return undefined;
@@ -879,9 +1009,8 @@ export namespace Doc {
// metadata field indicated by the title of the template field (not the default field that it was rendering)
//
export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
-
// find the metadata field key that this template field doc will display (indicated by its title)
- const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, "");
+ const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '');
// update the original template to mark it as a template
templateField.isTemplateForField = metadataFieldKey;
@@ -895,7 +1024,7 @@ export namespace Doc {
// note 2: this will not overwrite any field that already exists on the template doc at the field key
if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) {
Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
- (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue));
+ Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue);
}
// get the layout string that the template uses to specify its layout
const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
@@ -904,19 +1033,18 @@ export namespace Doc {
Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
// assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino)
- Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc);
+ Doc.Layout(templateField)[metadataFieldKey + '_ext'] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + '_ext'] as Doc);
return true;
}
-
// converts a document id to a url path on the server
- export function globalServerPath(doc: Doc | string = ""): string {
- return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc));
+ export function globalServerPath(doc: Doc | string = ''): string {
+ return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
}
// converts a document id to a url path on the server
export function localServerPath(doc?: Doc): string {
- return "/doc/" + (doc ? doc[Id] : "");
+ return '/doc/' + (doc ? doc[Id] : '');
}
export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
@@ -934,7 +1062,10 @@ export namespace Doc {
}
export function isBrushedHighlightedDegree(doc: Doc) {
- return Doc.IsHighlighted(doc) ? 6 : Doc.IsBrushedDegree(doc);
+ return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc);
+ }
+ export function isBrushedHighlightedDegreeUnmemoized(doc: Doc) {
+ return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegreeUnmemoized(doc);
}
export class DocBrush {
@@ -946,43 +1077,70 @@ export namespace Doc {
export class DocData {
@observable _user_doc: Doc = undefined!;
@observable _sharing_doc: Doc = undefined!;
- @observable _searchQuery: string = "";
+ @observable _searchQuery: string = '';
}
// the document containing the view layout information - will be the Document itself unless the Document has
// a layout field or 'layout' is given.
export function Layout(doc: Doc, layout?: Doc): Doc {
- const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null);
+ const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}-layout[` + layout[Id] + ']'], Doc, null);
return overrideLayout || doc[LayoutSym] || doc;
}
- export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; }
- export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
- export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; }
+ export function SetLayout(doc: Doc, layout: Doc | string) {
+ doc[StrCast(doc.layoutKey, 'layout')] = layout;
+ }
+ export function LayoutField(doc: Doc) {
+ return doc[StrCast(doc.layoutKey, 'layout')];
+ }
+ export function LayoutFieldKey(doc: Doc): string {
+ return StrCast(Doc.Layout(doc).layout).split("'")[1];
+ }
export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
}
- export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeWidth"], useWidth ? doc[WidthSym]() : 0)); }
- export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { return !doc ? 0 : NumCast(doc._nativeHeight, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0)); }
- export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeWidth"] = width; }
- export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeHeight"] = height; }
-
+ export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
+ return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0));
+ }
+ export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
+ const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0) : 0;
+ const nheight = doc ? (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]() : 0;
+ return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight);
+ }
+ export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
+ doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width;
+ }
+ export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
+ doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeHeight'] = height;
+ }
const manager = new DocData();
- export function SearchQuery(): string { return manager._searchQuery; }
- export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); }
- export function UserDoc(): Doc { return manager._user_doc; }
- export function SharingDoc(): Doc { return Cast(Doc.UserDoc().mySharedDocs, Doc, null); }
- export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); }
- export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); }
+ export function SearchQuery(): string {
+ return manager._searchQuery;
+ }
+ export function SetSearchQuery(query: string) {
+ runInAction(() => (manager._searchQuery = query));
+ }
+ export function UserDoc(): Doc {
+ return manager._user_doc;
+ }
+ export function SharingDoc(): Doc {
+ return Doc.MySharedDocs;
+ }
+ export function LinkDBDoc(): Doc {
+ return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
+ }
+ export function SetUserDoc(doc: Doc) {
+ return (manager._user_doc = doc);
+ }
const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
- brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
});
- export function IsSearchMatch(doc: Doc) { return isSearchMatchCache(doc); }
+ export function IsSearchMatch(doc: Doc) {
+ return isSearchMatchCache(doc);
+ }
export function IsSearchMatchUnmemoized(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
- brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
}
export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
@@ -1001,13 +1159,37 @@ export namespace Doc {
brushManager.SearchMatchDoc.clear();
}
- const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); });
- export function IsBrushed(doc: Doc) { return isBrushedCache(doc); }
+ const isBrushedCache = computedFn(function IsBrushed(doc: Doc) {
+ return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
+ });
+ export function IsBrushed(doc: Doc) {
+ return isBrushedCache(doc);
+ }
+ export enum DocBrushStatus {
+ unbrushed = 0,
+ protoBrushed = 1,
+ selfBrushed = 2,
+ highlighted = 3,
+ linkHighlighted = 4,
+ }
// don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
export function IsBrushedDegreeUnmemoized(doc: Doc) {
- if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
- return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return DocBrushStatus.unbrushed;
+ const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+ if (status === DocBrushStatus.unbrushed) {
+ const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement();
+ if (lastBrushed) {
+ for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) {
+ const a1 = Cast(link.anchor1, Doc, null);
+ const a2 = Cast(link.anchor2, Doc, null);
+ if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc) || Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc)) {
+ return DocBrushStatus.linkHighlighted;
+ }
+ }
+ }
+ }
+ return status;
}
export function IsBrushedDegree(doc: Doc) {
return computedFn(function IsBrushDegree(doc: Doc) {
@@ -1016,34 +1198,37 @@ export namespace Doc {
}
export function BrushDoc(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
- brushManager.BrushedDoc.set(doc, true);
- brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+ runInAction(() => {
+ brushManager.BrushedDoc.set(doc, true);
+ brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+ });
return doc;
}
export function UnBrushDoc(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
- brushManager.BrushedDoc.delete(doc);
- brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+ runInAction(() => {
+ brushManager.BrushedDoc.delete(doc);
+ brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+ });
return doc;
}
export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
- return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) ||
- Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? "1" : "2";
+ return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? '1' : '2';
}
export function linkFollowUnhighlight() {
Doc.UnhighlightAll();
- document.removeEventListener("pointerdown", linkFollowUnhighlight);
+ document.removeEventListener('pointerdown', linkFollowUnhighlight);
}
let _lastDate = 0;
- export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
+ export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true) {
linkFollowUnhighlight();
- Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
- document.removeEventListener("pointerdown", linkFollowUnhighlight);
- document.addEventListener("pointerdown", linkFollowUnhighlight);
- const lastDate = _lastDate = Date.now();
+ (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs));
+ document.removeEventListener('pointerdown', linkFollowUnhighlight);
+ document.addEventListener('pointerdown', linkFollowUnhighlight);
+ const lastDate = (_lastDate = Date.now());
window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
}
@@ -1074,53 +1259,57 @@ export namespace Doc {
const targetDoc = docEntry.value;
targetDoc && Doc.UnHighlightDoc(targetDoc);
}
-
}
export function UnBrushAllDocs() {
brushManager.BrushedDoc.clear();
}
export function getDocTemplate(doc?: Doc) {
- return !doc ? undefined :
- doc.isTemplateDoc ? doc :
- Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory :
- Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ?
- (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) :
- undefined;
+ return !doc
+ ? undefined
+ : doc.isTemplateDoc
+ ? doc
+ : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
+ ? doc.dragFactory
+ : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
+ ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc
+ ? Doc.Layout(doc).proto
+ : Doc.Layout(doc)
+ : undefined;
}
export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
if (Utils.HasTransparencyFilter(value)) {
- const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1);
+ const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1;
return isTransparent(StrCast(doc[key]));
}
- if (typeof value === "string") {
- value = value.replace(`,${Utils.noRecursionHack}`, "");
+ if (typeof value === 'string') {
+ value = value.replace(`,${Utils.noRecursionHack}`, '');
}
- const fieldVal = doc[key];
- if (Cast(fieldVal, listSpec("string"), []).length) {
- const vals = Cast(fieldVal, listSpec("string"), []);
+ const fieldVal = key === '#' ? (StrCast(doc.tags).includes(':#' + value + ':') ? StrCast(doc.tags) : undefined) : doc[key];
+ if (Cast(fieldVal, listSpec('string'), []).length) {
+ const vals = Cast(fieldVal, listSpec('string'), []);
const docs = vals.some(v => (v as any) instanceof Doc);
if (docs) return value === Field.toString(fieldVal as Field);
- return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
const fieldStr = Field.toString(fieldVal as Field);
return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
- export function deiconifyView(doc: any) {
- StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
+ export function deiconifyView(doc: Doc) {
+ StrCast(doc.layoutKey).split('_')[1] === 'icon' && setNativeView(doc);
}
export function setNativeView(doc: any) {
- const prevLayout = StrCast(doc.layoutKey).split("_")[1];
- const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : "";
- prevLayout === "icon" && (doc.deiconifyLayout = undefined);
- doc.layoutKey = deiconify || "layout";
+ const prevLayout = StrCast(doc.layoutKey).split('_')[1];
+ const deiconify = prevLayout === 'icon' && StrCast(doc.deiconifyLayout) ? 'layout_' + StrCast(doc.deiconifyLayout) : '';
+ prevLayout === 'icon' && (doc.deiconifyLayout = undefined);
+ doc.layoutKey = deiconify || 'layout';
}
export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: number[]) {
if (!container) return;
- const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []);
+ const docRangeFilters = Cast(container._docRangeFilters, listSpec('string'), []);
for (let i = 0; i < docRangeFilters.length; i += 3) {
if (docRangeFilters[i] === key) {
docRangeFilters.splice(i, 3);
@@ -1138,16 +1327,16 @@ export namespace Doc {
// filters document in a container collection:
// all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
- export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
+ export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
if (!container) return;
- const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters";
- const docFilters = Cast(container[filterField], listSpec("string"), []);
+ const filterField = '_' + (fieldSuffix ? fieldSuffix + '-' : '') + 'docFilters';
+ const docFilters = Cast(container[filterField], listSpec('string'), []);
runInAction(() => {
for (let i = 0; i < docFilters.length; i++) {
- const fields = docFilters[i].split(":"); // split key:value:modifier
- if (fields[0] === key && (fields[1] === value || modifiers === "match")) {
+ const fields = docFilters[i].split(':'); // split key:value:modifier
+ if (fields[0] === key && (fields[1] === value || modifiers === 'match')) {
if (fields[2] === modifiers && modifiers && fields[1] === value) {
- if (toggle) modifiers = "remove";
+ if (toggle) modifiers = 'remove';
else return;
}
docFilters.splice(i, 1);
@@ -1155,17 +1344,17 @@ export namespace Doc {
break;
}
}
- if (!docFilters.length && modifiers === "match" && value === undefined) {
+ if (!docFilters.length && modifiers === 'match' && value === undefined) {
container[filterField] = undefined;
- } else if (modifiers !== "remove") {
+ } else if (modifiers !== 'remove') {
!append && (docFilters.length = 0);
- docFilters.push(key + ":" + value + ":" + modifiers);
+ docFilters.push(key + ':' + value + ':' + modifiers);
container[filterField] = new List<string>(docFilters);
}
});
}
export function readDocRangeFilter(doc: Doc, key: string) {
- const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []);
+ const docRangeFilters = Cast(doc._docRangeFilters, listSpec('string'), []);
for (let i = 0; i < docRangeFilters.length; i += 3) {
if (docRangeFilters[i] === key) {
return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])];
@@ -1183,8 +1372,7 @@ export namespace Doc {
layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale;
layoutDoc._nativeWidth = undefined;
layoutDoc._nativeHeight = undefined;
- }
- else {
+ } else {
layoutDoc._autoHeight = false;
if (!Doc.NativeWidth(layoutDoc)) {
layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth);
@@ -1196,70 +1384,64 @@ export namespace Doc {
export function isDocPinned(doc: Doc) {
//add this new doc to props.Document
- const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
- return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1;
- }
-
- export function copyDragFactory(dragFactory: Doc) {
- const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
- ndoc && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(ndoc));
- if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
- dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
- Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
- }
-
- if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
-
- return ndoc;
- }
- export function delegateDragFactory(dragFactory: Doc) {
- const ndoc = Doc.MakeDelegateWithProto(dragFactory);
- if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
- dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
- Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
- }
- return ndoc;
+ const curPres = Doc.ActivePresentation;
+ return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1;
}
+ // prettier-ignore
export function toIcon(doc?: Doc, isOpen?: boolean) {
switch (StrCast(doc?.type)) {
- case DocumentType.IMG: return "image";
- case DocumentType.COMPARISON: return "columns";
- case DocumentType.RTF: return "sticky-note";
+ case DocumentType.IMG: return 'image';
+ case DocumentType.COMPARISON: return 'columns';
+ case DocumentType.RTF: return 'sticky-note';
case DocumentType.COL:
- const folder: IconProp = isOpen ? "folder-open" : "folder";
- const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right";
+ const folder: IconProp = isOpen ? 'folder-open' : 'folder';
+ const chevron: IconProp = isOpen ? 'chevron-down' : 'chevron-right';
return !doc?.isFolder ? folder : chevron;
- case DocumentType.WEB: return "globe-asia";
- case DocumentType.SCREENSHOT: return "photo-video";
- case DocumentType.WEBCAM: return "video";
- case DocumentType.AUDIO: return "microphone";
- case DocumentType.BUTTON: return "bolt";
- case DocumentType.PRES: return "tv";
- case DocumentType.SCRIPTING: return "terminal";
- case DocumentType.IMPORT: return "cloud-upload-alt";
- case DocumentType.VID: return "video";
- case DocumentType.INK: return "pen-nib";
- case DocumentType.PDF: return "file-pdf";
- case DocumentType.LINK: return "link";
- case DocumentType.MAP: return "map-marker-alt";
- default: return "question";
+ case DocumentType.WEB: return 'globe-asia';
+ case DocumentType.SCREENSHOT: return 'photo-video';
+ case DocumentType.WEBCAM: return 'video';
+ case DocumentType.AUDIO: return 'microphone';
+ case DocumentType.BUTTON: return 'bolt';
+ case DocumentType.PRES: return 'tv';
+ case DocumentType.SCRIPTING: return 'terminal';
+ case DocumentType.IMPORT: return 'cloud-upload-alt';
+ case DocumentType.VID: return 'video';
+ case DocumentType.INK: return 'pen-nib';
+ case DocumentType.PDF: return 'file-pdf';
+ case DocumentType.LINK: return 'link';
+ case DocumentType.MAP: return 'map-marker-alt';
+ default: return 'question';
}
}
+ export async function importDocument(file: File) {
+ const upload = Utils.prepend('/uploadDoc');
+ const formData = new FormData();
+ if (file) {
+ formData.append('file', file);
+ formData.append('remap', 'true');
+ const response = await fetch(upload, { method: 'POST', body: formData });
+ const json = await response.json();
+ if (json !== 'error') {
+ const doc = await DocServer.GetRefField(json);
+ return doc;
+ }
+ }
+ return undefined;
+ }
export namespace Get {
-
- const primitives = ["string", "number", "boolean"];
+ const primitives = ['string', 'number', 'boolean'];
export interface JsonConversionOpts {
data: any;
title?: string;
- appendToExisting?: { targetDoc: Doc, fieldKey?: string };
+ appendToExisting?: { targetDoc: Doc; fieldKey?: string };
excludeEmptyObjects?: boolean;
}
- const defaultKey = "json";
+ const defaultKey = 'json';
/**
* This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
@@ -1304,17 +1486,17 @@ export namespace Doc {
if (excludeEmptyObjects === undefined) {
excludeEmptyObjects = true;
}
- if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) {
+ if (data === undefined || data === null || ![...primitives, 'object'].includes(typeof data)) {
return undefined;
}
let resolved: any;
try {
- resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data));
+ resolved = JSON.parse(typeof data === 'string' ? data : JSON.stringify(data));
} catch (e) {
return undefined;
}
let output: Opt<Doc>;
- if (typeof resolved === "object" && !(resolved instanceof Array)) {
+ if (typeof resolved === 'object' && !(resolved instanceof Array)) {
output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc);
} else {
// give the proper types to the data extracted from the JSON
@@ -1322,7 +1504,7 @@ export namespace Doc {
if (appendToExisting) {
(output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result;
} else {
- (output = new Doc).json = result;
+ (output = new Doc()).json = result;
}
}
title && output && (output.title = title);
@@ -1339,14 +1521,14 @@ export namespace Doc {
const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
const hasEntries = Object.keys(object).length;
if (hasEntries || !excludeEmptyObjects) {
- const resolved = target ?? new Doc;
+ const resolved = target ?? new Doc();
if (hasEntries) {
let result: Opt<Field>;
Object.keys(object).map(key => {
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
// otherwise been empty (List or Doc)s will just not be written
- if (result = toField(object[key], excludeEmptyObjects, key)) {
+ if ((result = toField(object[key], excludeEmptyObjects, key))) {
resolved[key] = result;
}
});
@@ -1382,42 +1564,78 @@ export namespace Doc {
if (primitives.includes(typeof data)) {
return data;
}
- if (typeof data === "object") {
+ if (typeof data === 'object') {
return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined);
}
throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
};
}
-
}
-ScriptingGlobals.add(function idToDoc(id: string): any { return DocServer.GetCachedRefField(id); });
-ScriptingGlobals.add(function renameAlias(doc: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${doc.aliasNumber})`; });
-ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); });
-ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
-ScriptingGlobals.add(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
-ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
-ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); });
-ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); });
-ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); });
-ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); });
-ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
-ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
-ScriptingGlobals.add(function deiconifyView(doc: any) { Doc.deiconifyView(doc); });
-ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); });
-ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); });
-ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; });
-ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); });
-ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); });
+ScriptingGlobals.add(function idToDoc(id: string): any {
+ return DocServer.GetCachedRefField(id);
+});
+ScriptingGlobals.add(function renameAlias(doc: any) {
+ return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.aliasNumber})`;
+});
+ScriptingGlobals.add(function getProto(doc: any) {
+ return Doc.GetProto(doc);
+});
+ScriptingGlobals.add(function getDocTemplate(doc?: any) {
+ return Doc.getDocTemplate(doc);
+});
+ScriptingGlobals.add(function getAlias(doc: any) {
+ return Doc.MakeAlias(doc);
+});
+ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) {
+ return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto);
+});
+ScriptingGlobals.add(function copyField(field: any) {
+ return Field.Copy(field);
+});
+ScriptingGlobals.add(function docList(field: any) {
+ return DocListCast(field);
+});
+ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added: Doc) {
+ return Doc.AddDocToList(doc, field, added);
+});
+ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) {
+ return Doc.SetInPlace(doc, field, value, false);
+});
+ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
+ return Doc.AreProtosEqual(doc1, doc2);
+});
+ScriptingGlobals.add(function undo() {
+ SelectionManager.DeselectAll();
+ return UndoManager.Undo();
+});
+ScriptingGlobals.add(function redo() {
+ SelectionManager.DeselectAll();
+ return UndoManager.Redo();
+});
+ScriptingGlobals.add(function DOC(id: string) {
+ console.log("Can't parse a document id in a script");
+ return 'invalid';
+});
+ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
+ return Doc.assignDocToField(doc, field, id);
+});
+ScriptingGlobals.add(function docCast(doc: FieldResult): any {
+ return DocCastAsync(doc);
+});
ScriptingGlobals.add(function activePresentationItem() {
- const curPres = Doc.UserDoc().activePresentation as Doc;
+ const curPres = Doc.ActivePresentation;
return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
});
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
- const docs = SelectionManager.Views().map(dv => dv.props.Document).
- filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP &&
- (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
+ const docs = SelectionManager.Views()
+ .map(dv => dv.props.Document)
+ .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
return docs.length ? new List(docs) : prevValue;
});
-ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: "match" | "check" | "x" | "remove") { Doc.setDocFilter(container, key, value, modifiers); });
-ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { Doc.setDocRangeFilter(container, key, range); });
+ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
+ Doc.setDocFilter(container, key, value, modifiers);
+});
+ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
+ Doc.setDocRangeFilter(container, key, range);
+});
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 31024e805..114d5fc2f 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -11,7 +11,9 @@ export enum InkTool {
Pen = "pen",
Highlighter = "highlighter",
Eraser = "eraser",
- Stamp = "stamp"
+ Stamp = "stamp",
+ Write = "write",
+ PresentationPin = 'presentationpin'
}
@@ -83,7 +85,7 @@ export class InkField extends ObjectField {
}
[ToScriptString]() {
- return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}} `) + "])";
+ return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}`) + "])";
}
[ToString]() {
return "InkField";
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 60bf442d4..5cc4ca543 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,25 +1,25 @@
-import { action, observable } from "mobx";
-import { alias, list, serializable } from "serializr";
-import { DocServer } from "../client/DocServer";
-import { ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { afterDocDeserialize, autoObject, Deserializable } from "../client/util/SerializationHelper";
-import { Field } from "./Doc";
-import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
-import { ObjectField } from "./ObjectField";
-import { ProxyField } from "./Proxy";
-import { RefField } from "./RefField";
-import { listSpec } from "./Schema";
-import { Cast } from "./Types";
-import { deleteProperty, getter, setter, updateFunction } from "./util";
+import { action, observable } from 'mobx';
+import { alias, list, serializable } from 'serializr';
+import { DocServer } from '../client/DocServer';
+import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { afterDocDeserialize, autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { Field } from './Doc';
+import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
+import { ProxyField } from './Proxy';
+import { RefField } from './RefField';
+import { listSpec } from './Schema';
+import { Cast } from './Types';
+import { deleteProperty, getter, setter, updateFunction } from './util';
const listHandlers: any = {
/// Mutator methods
copyWithin() {
- throw new Error("copyWithin not supported yet");
+ throw new Error('copyWithin not supported yet');
},
fill(value: any, start?: number, end?: number) {
if (value instanceof RefField) {
- throw new Error("fill with RefFields not supported yet");
+ throw new Error('fill with RefFields not supported yet');
}
const res = this[Self].__fields.fill(value, start, end);
this[Update]();
@@ -32,6 +32,7 @@ const listHandlers: any = {
},
push: action(function (this: any, ...items: any[]) {
items = items.map(toObjectField);
+
const list = this[Self];
const length = list.__fields.length;
for (let i = 0; i < items.length; i++) {
@@ -43,7 +44,7 @@ const listHandlers: any = {
}
}
const res = list.__fields.push(...items);
- this[Update]({ op: "$addToSet", items, length: length + items.length });
+ this[Update]({ op: '$addToSet', items, length: length + items.length });
return res;
}),
reverse() {
@@ -77,8 +78,13 @@ const listHandlers: any = {
}
}
const res = list.__fields.splice(start, deleteCount, ...items);
- this[Update](items.length === 0 && deleteCount ? { op: "$remFromSet", items: removed, length: list.__fields.length } :
- items.length && !deleteCount && start === list.__fields.length ? { op: "$addToSet", items, length: list.__fields.length } : undefined);
+ this[Update](
+ items.length === 0 && deleteCount
+ ? { op: '$remFromSet', items: removed, length: list.__fields.length }
+ : items.length && !deleteCount && start === list.__fields.length
+ ? { op: '$addToSet', items, length: list.__fields.length }
+ : undefined
+ );
return res.map(toRealField);
}),
unshift(...items: any[]) {
@@ -97,7 +103,6 @@ const listHandlers: any = {
const res = this[Self].__fields.unshift(...items);
this[Update]();
return res;
-
},
/// Accessor methods
concat: action(function (this: any, ...items: any[]) {
@@ -197,7 +202,7 @@ const listHandlers: any = {
},
[Symbol.iterator]() {
return this[Self].__realFields().values();
- }
+ },
};
function toObjectField(field: Field) {
@@ -216,14 +221,14 @@ function listGetter(target: any, prop: string | number | symbol, receiver: any):
}
interface ListSpliceUpdate<T> {
- type: "splice";
+ type: 'splice';
index: number;
added: T[];
removedCount: number;
}
interface ListIndexUpdate<T> {
- type: "update";
+ type: 'update';
index: number;
newValue: T;
}
@@ -232,7 +237,7 @@ type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
-@Deserializable("list")
+@Deserializable('list')
class ListImpl<T extends Field> extends ObjectField {
constructor(fields?: T[]) {
super();
@@ -243,14 +248,16 @@ class ListImpl<T extends Field> extends ObjectField {
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target.__fields) {
return {
- configurable: true,//TODO Should configurable be true?
+ configurable: true, //TODO Should configurable be true?
enumerable: true,
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
},
deleteProperty: deleteProperty,
- defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+ defineProperty: () => {
+ throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
+ },
});
this[SelfProxy] = list;
if (fields) {
@@ -264,7 +271,7 @@ class ListImpl<T extends Field> extends ObjectField {
// this requests all ProxyFields at the same time to avoid the overhead
// of separate network requests and separate updates to the React dom.
private __realFields() {
- const promised = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()).map(f => ({ field: f as any, promisedFieldId: (f instanceof ProxyField) ? f.promisedValue() : "" }));
+ const promised = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()).map(f => ({ field: f as any, promisedFieldId: f instanceof ProxyField ? f.promisedValue() : '' }));
// if we find any ProxyFields that don't have a current value, then
// start the server request for all of them
if (promised.length) {
@@ -281,7 +288,7 @@ class ListImpl<T extends Field> extends ObjectField {
return this.__fields.map(toRealField);
}
- @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize })))
+ @serializable(alias('fields', list(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fields() {
return this.___fields;
}
@@ -298,7 +305,7 @@ class ListImpl<T extends Field> extends ObjectField {
}
[Copy]() {
- const copiedData = this[Self].__fields.map(f => f instanceof ObjectField ? f[Copy]() : f);
+ const copiedData = this[Self].__fields.map(f => (f instanceof ObjectField ? f[Copy]() : f));
const deepCopy = new ListImpl<T>(copiedData as any);
return deepCopy;
}
@@ -312,7 +319,7 @@ class ListImpl<T extends Field> extends ObjectField {
const update = this[OnUpdate];
// update && update(diff);
update?.(diff);
- }
+ };
private [Self] = this;
private [SelfProxy]: any;
@@ -327,9 +334,9 @@ class ListImpl<T extends Field> extends ObjectField {
export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
-ScriptingGlobals.add("List", List);
+ScriptingGlobals.add('List', List);
ScriptingGlobals.add(function compareLists(l1: any, l2: any) {
- const L1 = Cast(l1, listSpec("string"), []);
- const L2 = Cast(l2, listSpec("string"), []);
+ const L1 = Cast(l1, listSpec('string'), []);
+ const L2 = Cast(l2, listSpec('string'), []);
return !L1 && !L2 ? true : L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true);
-}, "compare two lists"); \ No newline at end of file
+}, 'compare two lists');
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index a19be5df9..bf055cd8b 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,48 +1,46 @@
-import { AssertionError } from "assert";
-import { docs_v1 } from "googleapis";
-import { Fragment, Mark, Node } from "prosemirror-model";
-import { sinkListItem } from "prosemirror-schema-list";
-import { Utils, DashColor } from "../Utils";
-import { Docs, DocUtils } from "../client/documents/Documents";
-import { schema } from "../client/views/nodes/formattedText/schema_rts";
-import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
-import { DocServer } from "../client/DocServer";
-import { Networking } from "../client/Network";
-import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox";
-import { Doc, Opt } from "./Doc";
-import { Id } from "./FieldSymbols";
-import { RichTextField } from "./RichTextField";
-import { Cast, StrCast } from "./Types";
+import { AssertionError } from 'assert';
+import { docs_v1 } from 'googleapis';
+import { Fragment, Mark, Node } from 'prosemirror-model';
+import { sinkListItem } from 'prosemirror-schema-list';
+import { Utils, DashColor } from '../Utils';
+import { Docs, DocUtils } from '../client/documents/Documents';
+import { schema } from '../client/views/nodes/formattedText/schema_rts';
+import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { DocServer } from '../client/DocServer';
+import { Networking } from '../client/Network';
+import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
+import { Doc, Opt } from './Doc';
+import { Id } from './FieldSymbols';
+import { RichTextField } from './RichTextField';
+import { Cast, StrCast } from './Types';
import Color = require('color');
-import { EditorState, TextSelection, Transaction } from "prosemirror-state";
-import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils";
+import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
+import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
export namespace RichTextUtils {
-
- const delimiter = "\n";
- const joiner = "";
-
+ const delimiter = '\n';
+ const joiner = '';
export const Initialize = (initial?: string) => {
const content: any[] = [];
const state = {
doc: {
- type: "doc",
+ type: 'doc',
content,
},
selection: {
- type: "text",
+ type: 'text',
anchor: 0,
- head: 0
- }
+ head: 0,
+ },
};
if (initial && initial.length) {
content.push({
- type: "paragraph",
+ type: 'paragraph',
content: {
- type: "text",
- text: initial
- }
+ type: 'text',
+ text: initial,
+ },
});
state.selection.anchor = state.selection.head = initial.length + 1;
}
@@ -56,8 +54,8 @@ export namespace RichTextUtils {
export const ToPlainText = (state: EditorState) => {
// Because we're working with plain text, just concatenate all paragraphs
const content = state.doc.content;
- const paragraphs: Node<any>[] = [];
- content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node));
+ const paragraphs: Node[] = [];
+ content.forEach(node => node.type.name === 'paragraph' && paragraphs.push(node));
// Functions to flatten ProseMirror paragraph objects (and their components) to plain text
// Concatentate paragraphs and string the result together
@@ -80,22 +78,21 @@ export namespace RichTextUtils {
// Preserve the current state, but re-write the content to be the blocks
const parsed = JSON.parse(oldState ? oldState.Data : Initialize());
parsed.doc.content = elements.map(text => {
- const paragraph: any = { type: "paragraph" };
- text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break
+ const paragraph: any = { type: 'paragraph' };
+ text.length && (paragraph.content = [{ type: 'text', marks: [], text }]); // An empty paragraph gets treated as a line break
return paragraph;
});
// If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it
- parsed.selection = { type: "text", anchor: 1, head: 1 };
+ parsed.selection = { type: 'text', anchor: 1, head: 1 };
// Export the ProseMirror-compatible state object we've just built
return JSON.stringify(parsed);
};
export namespace GoogleDocs {
-
export const Export = async (state: EditorState): Promise<GoogleApiClientUtils.Docs.Content> => {
- const nodes: (Node<any> | null)[] = [];
+ const nodes: (Node | null)[] = [];
const text = ToPlainText(state);
state.doc.content.forEach(node => {
if (!node.childCount) {
@@ -126,10 +123,10 @@ export namespace RichTextUtils {
return { baseUrl: embeddedObject.imageProperties!.contentUri! };
});
- const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems });
+ const uploads = await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems });
if (uploads.length !== mediaItems.length) {
- throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" });
+ throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: 'Error with internally uploading inlineObjects!' });
}
for (let i = 0; i < objects.length; i++) {
@@ -144,14 +141,14 @@ export namespace RichTextUtils {
title: embeddedObject.title || `Imported Image from ${document.title}`,
width,
url: Utils.prepend(_m.client),
- agnostic: Utils.prepend(agnostic.client)
+ agnostic: Utils.prepend(agnostic.client),
});
}
}
return inlineObjectMap;
};
- type BulletPosition = { value: number, sinks: number };
+ type BulletPosition = { value: number; sinks: number };
interface MediaItem {
baseUrl: string;
@@ -172,7 +169,7 @@ export namespace RichTextUtils {
const lists: ListGroup[] = [];
const indentMap = new Map<ListGroup, BulletPosition[]>();
let globalOffset = 0;
- const nodes: Node<any>[] = [];
+ const nodes: Node[] = [];
for (const element of structured) {
if (Array.isArray(element)) {
lists.push(element);
@@ -182,7 +179,7 @@ export namespace RichTextUtils {
const sinks = paragraph.bullet!;
positions.push({
value: position + globalOffset,
- sinks
+ sinks,
});
position += item.nodeSize;
globalOffset += 2 * sinks;
@@ -191,13 +188,13 @@ export namespace RichTextUtils {
indentMap.set(element, positions);
nodes.push(list(state.schema, items));
} else {
- if (element.contents.some(child => "inlineObjectId" in child)) {
+ if (element.contents.some(child => 'inlineObjectId' in child)) {
const group = element.contents;
group.forEach((child, i) => {
- let node: Opt<Node<any>>;
- if ("inlineObjectId" in child) {
+ let node: Opt<Node>;
+ if ('inlineObjectId' in child) {
node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote);
- } else if ("content" in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) {
+ } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) {
node = paragraphNode(state.schema, [child]);
}
if (node) {
@@ -215,7 +212,7 @@ export namespace RichTextUtils {
state = state.apply(state.tr.replaceWith(0, 2, nodes));
const sink = sinkListItem(state.schema.nodes.list_item);
- const dispatcher = (tr: Transaction) => state = state.apply(tr);
+ const dispatcher = (tr: Transaction) => (state = state.apply(tr));
for (const list of lists) {
for (const pos of indentMap.get(list)!) {
const resolved = state.doc.resolve(pos.value);
@@ -252,17 +249,17 @@ export namespace RichTextUtils {
};
const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
- return schema.node("list_item", null, paragraphNode(schema, runs));
+ return schema.node('list_item', null, paragraphNode(schema, runs));
};
const list = (schema: any, items: Node[]): Node => {
- return schema.node("ordered_list", { mapStyle: "bullet" }, items);
+ return schema.node('ordered_list', { mapStyle: 'bullet' }, items);
};
const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined);
const fragment = children.length ? Fragment.from(children) : undefined;
- return schema.node("paragraph", null, fragment);
+ return schema.node('paragraph', null, fragment);
};
const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => {
@@ -278,7 +275,7 @@ export namespace RichTextUtils {
} else {
docid = backingDocId;
}
- return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" });
+ return schema.node('image', { src, agnostic, width, docid, float: null, location: 'add:right' });
};
const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
@@ -287,10 +284,10 @@ export namespace RichTextUtils {
};
const StyleToMark = new Map<keyof docs_v1.Schema$TextStyle, keyof typeof schema.marks>([
- ["bold", "strong"],
- ["italic", "em"],
- ["foregroundColor", "pFontColor"],
- ["fontSize", "pFontSize"]
+ ['bold', 'strong'],
+ ['italic', 'em'],
+ ['foregroundColor', 'pFontColor'],
+ ['fontSize', 'pFontSize'],
]);
const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => {
@@ -301,21 +298,21 @@ export namespace RichTextUtils {
Object.keys(textStyle).forEach(key => {
let value: any;
const targeted = key as keyof docs_v1.Schema$TextStyle;
- if (value = textStyle[targeted]) {
+ if ((value = textStyle[targeted])) {
const attributes: any = {};
let converted = StyleToMark.get(targeted) || targeted;
value.url && (attributes.href = value.url);
if (value.color) {
const object = value.color.rgbColor;
- attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex();
+ attributes.color = Color.rgb(['red', 'green', 'blue'].map(color => object[color] * 255 || 0)).hex();
}
if (value.magnitude) {
attributes.fontSize = value.magnitude;
}
- if (converted === "weightedFontFamily") {
- converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman";
+ if (converted === 'weightedFontFamily') {
+ converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman';
}
const mapped = schema.marks[converted];
@@ -332,38 +329,38 @@ export namespace RichTextUtils {
};
const MarkToStyle = new Map<keyof typeof schema.marks, keyof docs_v1.Schema$TextStyle>([
- ["strong", "bold"],
- ["em", "italic"],
- ["pFontColor", "foregroundColor"],
- ["pFontSize", "fontSize"],
- ["timesNewRoman", "weightedFontFamily"],
- ["georgia", "weightedFontFamily"],
- ["comicSans", "weightedFontFamily"],
- ["tahoma", "weightedFontFamily"],
- ["impact", "weightedFontFamily"]
+ ['strong', 'bold'],
+ ['em', 'italic'],
+ ['pFontColor', 'foregroundColor'],
+ ['pFontSize', 'fontSize'],
+ ['timesNewRoman', 'weightedFontFamily'],
+ ['georgia', 'weightedFontFamily'],
+ ['comicSans', 'weightedFontFamily'],
+ ['tahoma', 'weightedFontFamily'],
+ ['impact', 'weightedFontFamily'],
]);
const ExportFontFamilyMapping = new Map<string, string>([
- ["timesNewRoman", "Times New Roman"],
- ["arial", "Arial"],
- ["georgia", "Georgia"],
- ["comicSans", "Comic Sans MS"],
- ["tahoma", "Tahoma"],
- ["impact", "Impact"]
+ ['timesNewRoman', 'Times New Roman'],
+ ['arial', 'Arial'],
+ ['georgia', 'Georgia'],
+ ['comicSans', 'Comic Sans MS'],
+ ['tahoma', 'Tahoma'],
+ ['impact', 'Impact'],
]);
const ImportFontFamilyMapping = new Map<string, string>([
- ["Times New Roman", "timesNewRoman"],
- ["Arial", "arial"],
- ["Georgia", "georgia"],
- ["Comic Sans MS", "comicSans"],
- ["Tahoma", "tahoma"],
- ["Impact", "impact"]
+ ['Times New Roman', 'timesNewRoman'],
+ ['Arial', 'arial'],
+ ['Georgia', 'georgia'],
+ ['Comic Sans MS', 'comicSans'],
+ ['Tahoma', 'tahoma'],
+ ['Impact', 'impact'],
]);
- const ignored = ["user_mark"];
+ const ignored = ['user_mark'];
- const marksToStyle = async (nodes: (Node<any> | null)[]): Promise<docs_v1.Schema$Request[]> => {
+ const marksToStyle = async (nodes: (Node | null)[]): Promise<docs_v1.Schema$Request[]> => {
const requests: docs_v1.Schema$Request[] = [];
let position = 1;
for (const node of nodes) {
@@ -376,25 +373,25 @@ export namespace RichTextUtils {
const information: LinkInformation = {
startIndex: position,
endIndex: position + nodeSize,
- textStyle
+ textStyle,
};
- let mark: Mark<any>;
+ let mark: Mark;
const markMap = BuildMarkMap(marks);
for (const markName of Object.keys(schema.marks)) {
if (ignored.includes(markName) || !(mark = markMap[markName])) {
continue;
}
- let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle;
+ let converted = MarkToStyle.get(markName) || (markName as keyof docs_v1.Schema$TextStyle);
let value: any = true;
if (!converted) {
continue;
}
const { attrs } = mark;
switch (converted) {
- case "link":
- let url = attrs.allLinks.length ? attrs.allLinks[0].href : "";
- const delimiter = "/doc/";
- const alreadyShared = "?sharing=true";
+ case 'link':
+ 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)) {
const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]);
if (linkDoc instanceof Doc) {
@@ -411,41 +408,43 @@ export namespace RichTextUtils {
textStyle.foregroundColor = fromRgb.blue;
textStyle.bold = true;
break;
- case "fontSize":
- value = { magnitude: attrs.fontSize, unit: "PT" };
+ case 'fontSize':
+ value = { magnitude: attrs.fontSize, unit: 'PT' };
break;
- case "foregroundColor":
+ case 'foregroundColor':
value = fromHex(attrs.color);
break;
- case "weightedFontFamily":
+ case 'weightedFontFamily':
value = { fontFamily: ExportFontFamilyMapping.get(markName) };
}
let matches: RegExpExecArray | null;
if ((matches = /p(\d+)/g.exec(markName)) !== null) {
- converted = "fontSize";
- value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" };
+ converted = 'fontSize';
+ value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' };
}
textStyle[converted] = value;
}
if (Object.keys(textStyle).length) {
requests.push(EncodeStyleUpdate(information));
}
- if (node.type.name === "image") {
+ if (node.type.name === 'image') {
const width = attrs.width;
- requests.push(await EncodeImage({
- startIndex: position + nodeSize - 1,
- uri: attrs.agnostic,
- width: Number(typeof width === "string" ? width.replace("px", "") : width)
- }));
+ requests.push(
+ await EncodeImage({
+ startIndex: position + nodeSize - 1,
+ uri: attrs.agnostic,
+ width: Number(typeof width === 'string' ? width.replace('px', '') : width),
+ })
+ );
}
position += nodeSize;
}
return requests;
};
- const BuildMarkMap = (marks: Mark<any>[]) => {
- const markMap: { [type: string]: Mark<any> } = {};
- marks.forEach(mark => markMap[mark.type.name] = mark);
+ const BuildMarkMap = (marks: readonly Mark[]) => {
+ const markMap: { [type: string]: Mark } = {};
+ marks.forEach(mark => (markMap[mark.type.name] = mark));
return markMap;
};
@@ -462,23 +461,21 @@ export namespace RichTextUtils {
}
namespace fromRgb {
-
export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => {
return {
color: {
rgbColor: {
red: red / 255,
green: green / 255,
- blue: blue / 255
- }
- }
+ blue: blue / 255,
+ },
+ },
};
};
export const red = convert(255, 0, 0);
export const green = convert(0, 255, 0);
export const blue = convert(0, 0, 255);
-
}
const fromHex = (color: string): docs_v1.Schema$OptionalColor => {
@@ -490,10 +487,10 @@ export namespace RichTextUtils {
const { startIndex, endIndex, textStyle } = information;
return {
updateTextStyle: {
- fields: "*",
+ fields: '*',
range: { startIndex, endIndex },
- textStyle
- } as docs_v1.Schema$UpdateTextStyleRequest
+ textStyle,
+ } as docs_v1.Schema$UpdateTextStyleRequest,
};
};
@@ -507,13 +504,12 @@ export namespace RichTextUtils {
return {
insertInlineImage: {
uri: baseUrls[0],
- objectSize: { width: { magnitude: width, unit: "PT" } },
- location: { index: startIndex }
- }
+ objectSize: { width: { magnitude: width, unit: 'PT' } },
+ location: { index: startIndex },
+ },
};
}
return {};
};
}
-
-} \ No newline at end of file
+}
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 40ca0ce22..68fb45987 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,29 +1,32 @@
-import { computedFn } from "mobx-utils";
-import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from "serializr";
-import { CompiledScript, CompileScript } from "../client/util/Scripting";
-import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { autoObject, Deserializable } from "../client/util/SerializationHelper";
-import { numberRange } from "../Utils";
-import { Doc, Field, Opt } from "./Doc";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { List } from "./List";
-import { ObjectField } from "./ObjectField";
-import { ProxyField } from "./Proxy";
-import { Cast, NumCast } from "./Types";
-import { Plugins } from "./util";
+import { computedFn } from 'mobx-utils';
+import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr';
+import { DocServer } from '../client/DocServer';
+import { CompiledScript, CompileScript, ScriptOptions } from '../client/util/Scripting';
+import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { numberRange } from '../Utils';
+import { Doc, Field, Opt } from './Doc';
+import { Copy, Id, ToScriptString, ToString } from './FieldSymbols';
+import { List } from './List';
+import { ObjectField } from './ObjectField';
+import { Cast, NumCast } from './Types';
+import { Plugins } from './util';
function optional(propSchema: PropSchema) {
- return custom(value => {
- if (value !== undefined) {
- return propSchema.serializer(value);
- }
- return SKIP;
- }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
- if (jsonValue !== undefined) {
- return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ return custom(
+ value => {
+ if (value !== undefined) {
+ return propSchema.serializer(value);
+ }
+ return SKIP;
+ },
+ (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
+ if (jsonValue !== undefined) {
+ return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ }
+ return SKIP;
}
- return SKIP;
- });
+ );
}
const optionsSchema = createSimpleSchema({
@@ -32,31 +35,19 @@ const optionsSchema = createSimpleSchema({
typecheck: true,
editable: true,
readonly: true,
- params: optional(map(primitive()))
+ params: optional(map(primitive())),
});
const scriptSchema = createSimpleSchema({
options: object(optionsSchema),
- originalScript: true
+ originalScript: true,
});
-async function deserializeScript(script: ScriptField) {
- const captures: ProxyField<Doc> = (script as any).captures;
- const cache = captures ? undefined : ScriptField.GetScriptFieldCache(script.script.originalScript);
- if (cache) return (script as any).script = cache;
- if (captures) {
- const doc = (await captures.value())!;
- const captured: any = {};
- const keys = Object.keys(doc);
- const vals = await Promise.all(keys.map(key => doc[key]) as any);
- keys.forEach((key, i) => captured[key] = vals[i]);
- (script.script.options as any).capturedVariables = captured;
- }
+function finalizeScript(script: ScriptField, captures: boolean) {
const comp = CompileScript(script.script.originalScript, script.script.options);
if (!comp.compiled) {
throw new Error("Couldn't compile loaded script");
}
- (script as any).script = comp;
!captures && ScriptField._scriptFieldCache.set(script.script.originalScript, comp);
if (script.setterscript) {
const compset = CompileScript(script.setterscript?.originalScript, script.setterscript.options);
@@ -65,32 +56,56 @@ async function deserializeScript(script: ScriptField) {
}
(script as any).setterscript = compset;
}
+ return comp;
+}
+async function deserializeScript(script: ScriptField) {
+ if (script.captures) {
+ const captured: any = {};
+ (script.script.options as ScriptOptions).capturedVariables = captured;
+ Promise.all(
+ script.captures.map(async capture => {
+ const key = capture.split(':')[0];
+ const val = capture.split(':')[1];
+ if (val === 'true') captured[key] = true;
+ else if (val === 'false') captured[key] = false;
+ else if (val.startsWith('ID->')) captured[key] = await DocServer.GetRefField(val.replace('ID->', ''));
+ else if (!isNaN(Number(val))) captured[key] = Number(val);
+ else captured[key] = val;
+ })
+ ).then(() => ((script as any).script = finalizeScript(script, true)));
+ } else {
+ (script as any).script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script, false);
+ }
}
@scriptingGlobal
-@Deserializable("script", deserializeScript)
+@Deserializable('script', deserializeScript)
export class ScriptField extends ObjectField {
+ @serializable
+ readonly rawscript: string | undefined;
@serializable(object(scriptSchema))
readonly script: CompiledScript;
@serializable(object(scriptSchema))
readonly setterscript: CompiledScript | undefined;
@serializable(autoObject())
- private captures?: ProxyField<Doc>;
+ captures?: List<string>;
public static _scriptFieldCache: Map<string, Opt<CompiledScript>> = new Map();
- public static GetScriptFieldCache(field: string) { return this._scriptFieldCache.get(field); }
+ public static GetScriptFieldCache(field: string) {
+ return this._scriptFieldCache.get(field);
+ }
- constructor(script: CompiledScript, setterscript?: CompiledScript) {
+ constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) {
super();
- if (script?.options.capturedVariables) {
- const doc = Doc.assign(new Doc, script.options.capturedVariables);
- doc.system = true;
- this.captures = new ProxyField(doc);
+ const captured = script?.options.capturedVariables;
+ if (captured) {
+ this.captures = new List<string>(Object.keys(captured).map(key => key + ':' + (captured[key] instanceof Doc ? 'ID->' + (captured[key] as Doc)[Id] : captured[key].toString())));
}
+ this.rawscript = rawscript;
this.setterscript = setterscript;
- this.script = script;
+ this.script = script ?? (CompileScript('false') as CompiledScript);
}
// init(callback: (res: Field) => any) {
@@ -115,63 +130,62 @@ export class ScriptField extends ObjectField {
// }
[Copy](): ObjectField {
- return new ScriptField(this.script, this.setterscript);
+ return new ScriptField(this.script, this.setterscript, this.rawscript);
}
toString() {
return `${this.script.originalScript} + ${this.setterscript?.originalScript}`;
}
[ToScriptString]() {
- return "script field";
+ return 'script field';
}
[ToString]() {
return this.script.originalScript;
}
- public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) {
+ public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = CompileScript(script, {
params: {
- this: Doc?.name || "Doc", // this is the doc that executes the script
- self: Doc?.name || "Doc", // self is the root doc of the doc that executes the script
- _last_: "any", // _last_ is the previous value of a computed field when it is being triggered to re-run.
- _readOnly_: "boolean", // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox)
- ...params
+ this: Doc?.name || 'Doc', // this is the doc that executes the script
+ self: Doc?.name || 'Doc', // self is the root doc of the doc that executes the script
+ _last_: 'any', // _last_ is the previous value of a computed field when it is being triggered to re-run.
+ _readOnly_: 'boolean', // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox)
+ ...params,
},
typecheck: false,
editable: true,
addReturn: addReturn,
- capturedVariables
+ capturedVariables,
});
return compiled;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
- public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
}
@scriptingGlobal
-@Deserializable("computed", deserializeScript)
+@Deserializable('computed', deserializeScript)
export class ComputedField extends ScriptField {
_lastComputedResult: any;
//TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
- _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result;
-
+ _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
[Copy](): ObjectField {
- return new ComputedField(this.script, this.setterscript);
+ return new ComputedField(this.script, this.setterscript, this.rawscript);
}
public static MakeScript(script: string, params: object = {}) {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
@@ -182,7 +196,7 @@ export class ComputedField extends ScriptField {
doc[`${fieldKey}-indexed`] = flist;
}
const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
- const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: "any" }, true, {});
+ const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
}
}
@@ -196,7 +210,7 @@ export namespace ComputedField {
useComputed = true;
}
- export const undefined = "__undefined";
+ export const undefined = '__undefined';
export function WithoutComputed<T>(fn: () => T) {
DisableComputedFields();
@@ -216,15 +230,27 @@ export namespace ComputedField {
}
}
-ScriptingGlobals.add(function setIndexVal(list: any[], index: number, value: any) {
- while (list.length <= index) list.push(undefined);
- list[index] = value;
-}, "sets the value at a given index of a list", "(list: any[], index: number, value: any)");
+ScriptingGlobals.add(
+ function setIndexVal(list: any[], index: number, value: any) {
+ while (list.length <= index) list.push(undefined);
+ list[index] = value;
+ },
+ 'sets the value at a given index of a list',
+ '(list: any[], index: number, value: any)'
+);
-ScriptingGlobals.add(function getIndexVal(list: any[], index: number) {
- return list?.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any);
-}, "returns the value at a given index of a list", "(list: any[], index: number)");
+ScriptingGlobals.add(
+ function getIndexVal(list: any[], index: number) {
+ return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), undefined as any);
+ },
+ 'returns the value at a given index of a list',
+ '(list: any[], index: number)'
+);
-ScriptingGlobals.add(function makeScript(script: string) {
- return ScriptField.MakeScript(script);
-}, "returns the value at a given index of a list", "(list: any[], index: number)");
+ScriptingGlobals.add(
+ function makeScript(script: string) {
+ return ScriptField.MakeScript(script);
+ },
+ 'returns the value at a given index of a list',
+ '(list: any[], index: number)'
+);
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index c90f3b6b3..bf40a0d7b 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -4,6 +4,8 @@ import { RefField } from "./RefField";
import { DateField } from "./DateField";
import { ScriptField } from "./ScriptField";
import { URLField, WebField, ImageField } from "./URLField";
+import { TextField } from "@material-ui/core";
+import { RichTextField } from "./RichTextField";
export type ToType<T extends InterfaceValue> =
T extends "string" ? string :
@@ -74,6 +76,10 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal
return defaultVal === null ? undefined : defaultVal;
}
+export function DocCast(field: FieldResult, defaultVal?: Doc) {
+ return Cast(field, Doc, null) ?? defaultVal;
+}
+
export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
return Cast(field, "number", defaultVal);
}
@@ -88,6 +94,9 @@ export function BoolCast(field: FieldResult, defaultVal: boolean | null = false)
export function DateCast(field: FieldResult) {
return Cast(field, DateField, null);
}
+export function RTFCast(field: FieldResult) {
+ return Cast(field, RichTextField, null);
+}
export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) {
return Cast(field, ScriptField, defaultVal);
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 1d4bbaed0..00c78e231 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -1,16 +1,14 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, custom } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { ToScriptString, ToString, Copy } from "./FieldSymbols";
-import { scriptingGlobal } from "../client/util/ScriptingGlobals";
-import { Utils } from "../Utils";
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, custom } from 'serializr';
+import { ObjectField } from './ObjectField';
+import { ToScriptString, ToString, Copy } from './FieldSymbols';
+import { scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Utils } from '../Utils';
function url() {
return custom(
function (value: URL) {
- return value.origin === window.location.origin ?
- value.pathname :
- value.href;
+ return value?.origin === window.location.origin ? value.pathname : value?.href;
},
function (jsonValue: string) {
return new URL(jsonValue, window.location.origin);
@@ -26,23 +24,23 @@ export abstract class URLField extends ObjectField {
constructor(url: URL);
constructor(url: URL | string) {
super();
- if (typeof url === "string") {
- url = url.startsWith("http") ? new URL(url) : new URL(url, window.location.origin);
+ if (typeof url === 'string') {
+ url = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin);
}
this.url = url;
}
[ToScriptString]() {
- if (Utils.prepend(this.url.pathname) === this.url.href) {
+ if (Utils.prepend(this.url?.pathname) === this.url?.href) {
return `new ${this.constructor.name}("${this.url.pathname}")`;
}
return `new ${this.constructor.name}("${this.url.href}")`;
}
[ToString]() {
- if (Utils.prepend(this.url.pathname) === this.url.href) {
+ if (Utils.prepend(this.url?.pathname) === this.url?.href) {
return this.url.pathname;
}
- return this.url.href;
+ return this.url?.href;
}
[Copy](): this {
@@ -50,14 +48,35 @@ export abstract class URLField extends ObjectField {
}
}
-export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short.ogg";
-
-@scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { }
-@scriptingGlobal @Deserializable("image") export class ImageField extends URLField { }
-@scriptingGlobal @Deserializable("video") export class VideoField extends URLField { }
-@scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { }
-@scriptingGlobal @Deserializable("web") export class WebField extends URLField { }
-@scriptingGlobal @Deserializable("map") export class MapField extends URLField { }
-@scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { }
-@scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { }
+export const nullAudio = 'https://actions.google.com/sounds/v1/alarms/beep_short.ogg';
+@scriptingGlobal
+@Deserializable('audio')
+export class AudioField extends URLField {}
+@scriptingGlobal
+@Deserializable('recording')
+export class RecordingField extends URLField {}
+@scriptingGlobal
+@Deserializable('image')
+export class ImageField extends URLField {}
+@scriptingGlobal
+@Deserializable('video')
+export class VideoField extends URLField {}
+@scriptingGlobal
+@Deserializable('pdf')
+export class PdfField extends URLField {}
+@scriptingGlobal
+@Deserializable('web')
+export class WebField extends URLField {}
+@scriptingGlobal
+@Deserializable('map')
+export class MapField extends URLField {}
+@scriptingGlobal
+@Deserializable('csv')
+export class CsvField extends URLField {}
+@scriptingGlobal
+@Deserializable('youtube')
+export class YoutubeField extends URLField {}
+@scriptingGlobal
+@Deserializable('webcam')
+export class WebCamField extends URLField {}
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 4d5ae1018..be39e0709 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -36,15 +36,14 @@ export const documentSchema = createSchema({
_nativeHeight: "number", // "
_width: "number", // width of document in its container's coordinate system
_height: "number", // "
- _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
- _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set
+ _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set
+ _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set
_xMargin: "number", // margin added on left/right of most documents to add separation from their container
_yMargin: "number", // margin added on top/bottom of most documents to add separation from their container
_overflow: "string", // sets overflow behvavior for CollectionFreeForm views
_showCaption: "string", // whether editable caption text is overlayed at the bottom of the document
_showTitle: "string", // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover
_showAudio: "boolean", // whether to show the audio record icon on documents
- _freeformLOD: "boolean", // whether to enable LOD switching for CollectionFreeFormViews
_pivotField: "string", // specifies which field key should be used as the timeline/pivot axis
_columnsFill: "boolean", // whether documents in a stacking view column should be sized to fill the column
_columnsSort: "string", // how a document should be sorted "ascending", "descending", undefined (none)
@@ -60,7 +59,7 @@ export const documentSchema = createSchema({
borderRounding: "string", // border radius rounding of document
boxShadow: "string", // the amount of shadow around the perimeter of a document
color: "string", // foreground color of document
- fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view
+ fitContentsToBox: "boolean",// whether freeform view contents should be zoomed/panned to fill the area of the document view box
fontSize: "string",
hidden: "boolean", // whether a document should not be displayed
isInkMask: "boolean", // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through)
diff --git a/src/fields/util.ts b/src/fields/util.ts
index c708affe3..d87bb6656 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,20 +1,41 @@
-import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAugment, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing, AclSelfEdit } from "./Doc";
-import { SerializationHelper } from "../client/util/SerializationHelper";
-import { ProxyField, PrefetchProxy } from "./Proxy";
-import { RefField } from "./RefField";
-import { ObjectField } from "./ObjectField";
-import { action, trace, } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
-import { DocServer } from "../client/DocServer";
-import { ComputedField } from "./ScriptField";
-import { ScriptCast, StrCast } from "./Types";
-import { returnZero } from "../Utils";
-import CursorField from "./CursorField";
-import { List } from "./List";
-import { SnappingManager } from "../client/util/SnappingManager";
-import { computedFn } from "mobx-utils";
-import { RichTextField } from "./RichTextField";
+import { action, observable, runInAction, trace } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DocServer } from '../client/DocServer';
+import { SerializationHelper } from '../client/util/SerializationHelper';
+import { UndoManager } from '../client/util/UndoManager';
+import { returnZero } from '../Utils';
+import CursorField from './CursorField';
+import {
+ AclAdmin,
+ AclAugment,
+ AclEdit,
+ AclPrivate,
+ AclReadonly,
+ AclSelfEdit,
+ AclSym,
+ AclUnset,
+ DataSym,
+ Doc,
+ DocListCast,
+ DocListCastAsync,
+ FieldResult,
+ ForceServerWrite,
+ HeightSym,
+ Initializing,
+ LayoutSym,
+ updateCachedAcls,
+ UpdatingFromServer,
+ WidthSym,
+} from './Doc';
+import { Id, OnUpdate, Parent, Self, SelfProxy, Update } from './FieldSymbols';
+import { List } from './List';
+import { ObjectField } from './ObjectField';
+import { PrefetchProxy, ProxyField } from './Proxy';
+import { RefField } from './RefField';
+import { RichTextField } from './RichTextField';
+import { SchemaHeaderField } from './SchemaHeaderField';
+import { ComputedField } from './ScriptField';
+import { ScriptCast, StrCast } from './Types';
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -44,7 +65,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
return true;
}
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
target[prop] = value;
return true;
}
@@ -76,33 +97,35 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
- const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
- const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer =
- (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && (value instanceof RichTextField))) &&
- !DocServer.Control.isReadOnly();
+ const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
+ const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly;
+ const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly();
if (writeToDoc) {
if (value === undefined) {
- target.__fieldKeys && (delete target.__fieldKeys[prop]);
+ target.__fieldKeys && delete target.__fieldKeys[prop];
delete target.__fields[prop];
} else {
target.__fieldKeys && (target.__fieldKeys[prop] = true);
+ // if (target.__fields[prop] !== value) {
+ // console.log("ASSIGN " + prop + " " + value);
+ // }
target.__fields[prop] = value;
}
//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) } });
+ if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } });
+ else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } });
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
}
- !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
+ !receiver[Initializing] &&
+ (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
UndoManager.AddEvent({
- redo: () => receiver[prop] = value,
- undo: () => receiver[prop] = curValue,
- prop: prop?.toString()
+ redo: () => (receiver[prop] = value),
+ undo: () => (receiver[prop] = curValue),
+ prop: prop?.toString(),
});
return true;
}
@@ -133,76 +156,95 @@ export function denormalizeEmail(email: string) {
// playgroundMode = !playgroundMode;
// }
-
/**
* Copies parent's acl fields to the child
*/
export function inheritParentAcls(parent: Doc, child: Doc) {
+ return;
const dataDoc = parent[DataSym];
for (const key of Object.keys(dataDoc)) {
// if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
- const permission = (key === "acl-Public" && Doc.UserDoc().defaultAclPrivate) ? AclPrivate : dataDoc[key];
- key.startsWith("acl") && distributeAcls(key, permission, child);
+ const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
+ key.startsWith('acl') && distributeAcls(key, permission, child);
}
}
/**
* These are the various levels of access a user can have to a document.
- *
+ *
* Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
- *
+ *
* Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
- *
+ *
* Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything.
- *
+ *
* View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
- *
+ *
* None: the document is not shared with that user.
*/
export enum SharingPermissions {
- Admin = "Admin",
- Edit = "Edit",
- SelfEdit = "Self Edit",
- Augment = "Augment",
- View = "View",
- None = "Not Shared"
+ Admin = 'Admin',
+ Edit = 'Edit',
+ SelfEdit = 'Self Edit',
+ Augment = 'Augment',
+ View = 'View',
+ None = 'Not Shared',
}
// return acl from cache or cache the acl and return.
-const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, user); }, true);
+const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
+ return getEffectiveAcl(target, user);
+}, true);
/**
* Calculates the effective access right to a document for the current user.
*/
export function GetEffectiveAcl(target: any, user?: string): symbol {
- return !target ? AclPrivate :
- target[UpdatingFromServer] ? AclAdmin : getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
+ if (!target) return AclPrivate;
+ if (target[UpdatingFromServer]) return AclAdmin;
+ // authored documents are private until an ACL is set.
+ if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate;
+ return getEffectiveAclCache(target, user); // all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
}
function getPropAcl(target: any, prop: string | symbol | number) {
- if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
+ if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable
return GetEffectiveAcl(target);
}
let HierarchyMapping: Map<symbol, number> | undefined;
+let cachedGroups = observable([] as string[]);
+/// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts
+// need to investigate further what caused the mobx update problems and move to a better location.
+const getCachedGroupByNameCache = computedFn(function (name: string) {
+ return cachedGroups.includes(name);
+}, true);
+export function GetCachedGroupByName(name: string) {
+ return getCachedGroupByNameCache(name);
+}
+export function SetCachedGroups(groups: string[]) {
+ runInAction(() => cachedGroups.push(...groups));
+}
function getEffectiveAcl(target: any, user?: string): symbol {
const targetAcls = target[AclSym];
- const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
- const targetAuthor = (target.__fields?.author || target.author); // target may be a Doc of Proxy, so check __fields.author and .author
+ const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
+ const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author
if (userChecked === targetAuthor || !targetAuthor) return AclAdmin;
- if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin;
+ if (GetCachedGroupByName('Admin')) return AclAdmin;
if (targetAcls && Object.keys(targetAcls).length) {
- HierarchyMapping = HierarchyMapping || new Map<symbol, number>([
- [AclPrivate, 0],
- [AclReadonly, 1],
- [AclAugment, 2],
- [AclSelfEdit, 2.5],
- [AclEdit, 3],
- [AclAdmin, 4]
- ]);
+ HierarchyMapping =
+ HierarchyMapping ||
+ new Map<symbol, number>([
+ [AclPrivate, 0],
+ [AclReadonly, 1],
+ [AclAugment, 2],
+ [AclSelfEdit, 2.5],
+ [AclEdit, 3],
+ [AclAdmin, 4],
+ ]);
let effectiveAcl = AclPrivate;
for (const [key, value] of Object.entries(targetAcls)) {
@@ -210,14 +252,14 @@ function getEffectiveAcl(target: any, user?: string): symbol {
// as a result we need to restore them again during this comparison.
const entity = denormalizeEmail(key.substring(4)); // an individual or a group
if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
- if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) {
+ if (GetCachedGroupByName(entity) || userChecked === entity) {
effectiveAcl = value as symbol;
}
}
}
// if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document
- const override = targetAcls["acl-Override"];
+ const override = targetAcls['acl-Override'];
if (override !== AclUnset && override !== undefined) effectiveAcl = override;
// if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl)
@@ -239,12 +281,12 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
visited.push(target);
const HierarchyMapping = new Map<string, number>([
- ["Not Shared", 0],
- ["Can View", 1],
- ["Can Augment", 2],
- ["Self Edit", 2.5],
- ["Can Edit", 3],
- ["Admin", 4]
+ ['Not Shared', 0],
+ ['Can View', 1],
+ ['Can Augment', 2],
+ ['Self Edit', 2.5],
+ ['Can Edit', 3],
+ ['Admin', 4],
]);
let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
@@ -264,7 +306,6 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
}
if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
-
if (GetEffectiveAcl(dataDoc) === AclAdmin) {
dataDoc[key] = acl;
dataDocChanged = true;
@@ -275,7 +316,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
links.forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
// maps over the children of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? "-all" : "")]).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? '-all' : '')]).map(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
@@ -285,7 +326,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
});
// maps over the annotations of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '-annotations']).map(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
@@ -304,11 +345,11 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
const effectiveAcl = getPropAcl(target, prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
// if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
- if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "None"].includes(value))) return true;
+ if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, 'None'].includes(value))) return true;
// if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
- if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) {
- if (!prop.startsWith("__")) prop = prop.substring(1);
+ if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
+ if (!prop.startsWith('__')) prop = prop.substring(1);
if (target.__LAYOUT__) {
target.__LAYOUT__[prop] = value;
return true;
@@ -324,17 +365,18 @@ export function getter(target: any, in_prop: string | symbol | number, receiver:
let prop = in_prop;
if (in_prop === AclSym) return target[AclSym];
- if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop];
+ if (in_prop === 'toString' || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === 'symbol')) return target.__fields[prop] || target[prop];
if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
if (prop === LayoutSym) return target.__LAYOUT__;
- if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) {
- if (!prop.startsWith("__")) prop = prop.substring(1);
+ if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
+ if (!prop.startsWith('__')) prop = prop.substring(1);
if (target.__LAYOUT__) return target.__LAYOUT__[prop];
}
- if (prop === "then") {//If we're being awaited
+ if (prop === 'then') {
+ //If we're being awaited
return undefined;
}
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
return target.__fields[prop] || target[prop];
}
if (SerializationHelper.IsSerializing()) {
@@ -355,22 +397,21 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
field = res.value;
}
}
- if (field === undefined && !ignoreProto && prop !== "proto") {
- const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
+ if (field === undefined && !ignoreProto && prop !== 'proto') {
+ const proto = getFieldImpl(target, 'proto', receiver, true); //TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
}
return undefined;
}
return field;
-
}
export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
return getFieldImpl(target, prop, undefined, ignoreProto);
}
export function deleteProperty(target: any, prop: string | number | symbol) {
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
delete target[prop];
return true;
}
@@ -382,64 +423,73 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
let lastValue = ObjectField.MakeCopy(value);
return (diff?: any) => {
const op =
- diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } :
- diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
- : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
+ diff?.op === '$addToSet'
+ ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
+ : diff?.op === '$remFromSet'
+ ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
+ : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } };
!op.$set && ((op as any).length = diff.length);
const prevValue = ObjectField.MakeCopy(lastValue as List<any>);
lastValue = ObjectField.MakeCopy(value);
const newValue = ObjectField.MakeCopy(value);
- if (!(value instanceof CursorField) && !(value?.some?.((v: any) => v instanceof CursorField))) {
- !receiver[UpdatingFromServer] && UndoManager.AddEvent(
- diff?.op === "$addToSet" ?
- {
- redo: () => {
- receiver[prop].push(...diff.items.map((item: any) => item.value ? item.value() : item));
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: action(() => {
- // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- prop: ""
- } :
- diff?.op === "$remFromSet" ?
- {
- redo: action(() => {
- diff.items.forEach((item: any) => {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- undo: () => {
- // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: ""
- }
+ if (!(value instanceof CursorField) && !value?.some?.((v: any) => v instanceof CursorField)) {
+ !receiver[UpdatingFromServer] &&
+ UndoManager.AddEvent(
+ diff?.op === '$addToSet'
+ ? {
+ redo: () => {
+ receiver[prop].push(...diff.items.map((item: any) => (item.value ? item.value() : item)));
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ undo: action(() => {
+ // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
+ diff.items.forEach((item: any) => {
+ const ind = receiver[prop].indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ }),
+ prop: '',
+ }
+ : diff?.op === '$remFromSet'
+ ? {
+ redo: action(() => {
+ diff.items.forEach((item: any) => {
+ const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ }),
+ undo: () => {
+ // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
+ diff.items.forEach((item: any) => {
+ if (item instanceof SchemaHeaderField) {
+ const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
+ ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item);
+ } else {
+ const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
+ }
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ prop: '',
+ }
: {
- redo: () => {
- receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: () => {
- // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
- receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: ""
- });
+ redo: () => {
+ receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ undo: () => {
+ // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
+ receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ prop: '',
+ }
+ );
}
target[Update](op);
};
-} \ No newline at end of file
+}
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 88221732e..64baa351c 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -19,7 +19,7 @@ import React = require('react');
@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;
+ @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, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
/**
* Handles the onclick functionality for the 'Restart' button
@@ -36,7 +36,7 @@ export class AudioUpload extends React.Component {
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;
+ }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
}
/**
@@ -96,7 +96,6 @@ export class AudioUpload extends React.Component {
isDocumentActive={returnTrue}
isContentActive={emptyFunction}
focus={emptyFunction}
- layerProvider={undefined}
styleProvider={() => "rgba(0,0,0,0)"}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index d668d134e..6415099fd 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,13 +1,12 @@
import React = require('react');
-import { observer } from "mobx-react";
-import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message";
-import { observable, action } from "mobx";
-import { GestureUtils } from "../pen-gestures/GestureUtils";
-import "./MobileInkOverlay.scss";
-import { DragManager } from "../client/util/DragManager";
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
import { DocServer } from '../client/DocServer';
+import { DragManager } from '../client/util/DragManager';
import { Doc } from '../fields/Doc';
-
+import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message';
+import './MobileInkOverlay.scss';
@observer
export default class MobileInkOverlay extends React.Component {
@@ -18,7 +17,7 @@ export default class MobileInkOverlay extends React.Component {
@observable private _height: number = 0;
@observable private _x: number = -300;
@observable private _y: number = -300;
- @observable private _text: string = "";
+ @observable private _text: string = '';
@observable private _offsetX: number = 0;
@observable private _offsetY: number = 0;
@@ -49,7 +48,7 @@ export default class MobileInkOverlay extends React.Component {
this._scale = scaledSize.scale;
this._x = 300; // TODO: center on screen
this._y = 25; // TODO: center on screen
- this._text = text ? text : "";
+ this._text = text ? text : '';
}
@action
@@ -64,31 +63,29 @@ export default class MobileInkOverlay extends React.Component {
// TODO: figure out why strokes drawn in corner of mobile interface dont get inserted
const { points, bounds } = content;
- console.log("received points", points, bounds);
+ console.log('received points', points, bounds);
const B = {
- right: (bounds.right * this._scale) + this._x,
- left: (bounds.left * this._scale) + this._x, // TODO: scale
- bottom: (bounds.bottom * this._scale) + this._y,
- top: (bounds.top * this._scale) + this._y, // TODO: scale
+ right: bounds.right * this._scale + this._x,
+ left: bounds.left * this._scale + this._x, // TODO: scale
+ bottom: bounds.bottom * this._scale + this._y,
+ top: bounds.top * this._scale + this._y, // TODO: scale
width: bounds.width * this._scale,
height: bounds.height * this._scale,
};
const target = document.elementFromPoint(this._x + 10, this._y + 10);
target?.dispatchEvent(
- new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
- {
- bubbles: true,
- detail: {
- points: points,
- gesture: GestureUtils.Gestures.Stroke,
- bounds: B
- }
- }
- )
+ new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
+ bubbles: true,
+ detail: {
+ points: points,
+ gesture: GestureUtils.Gestures.Stroke,
+ bounds: B,
+ },
+ })
);
- }
+ };
uploadDocument = async (content: MobileDocumentUploadContent) => {
const { docId } = content;
@@ -100,36 +97,34 @@ export default class MobileInkOverlay extends React.Component {
const complete = new DragManager.DragCompleteEvent(false, dragData);
if (target) {
- console.log("dispatching upload doc!!!!", target, doc);
+ console.log('dispatching upload doc!!!!', target, doc);
target.dispatchEvent(
- new CustomEvent<DragManager.DropEvent>("dashOnDrop",
- {
- bubbles: true,
- detail: {
- x: this._x,
- y: this._y,
- complete: complete,
- altKey: false,
- metaKey: false,
- ctrlKey: false,
- shiftKey: false,
- embedKey: false
- }
- }
- )
+ new CustomEvent<DragManager.DropEvent>('dashOnDrop', {
+ bubbles: true,
+ detail: {
+ x: this._x,
+ y: this._y,
+ complete: complete,
+ altKey: false,
+ metaKey: false,
+ ctrlKey: false,
+ shiftKey: false,
+ embedKey: false,
+ },
+ })
);
} else {
- alert("TARGET IS UNDEFINED");
+ alert('TARGET IS UNDEFINED');
}
}
- }
+ };
@action
dragStart = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
- document.addEventListener("pointermove", this.dragging);
- document.addEventListener("pointerup", this.dragEnd);
+ document.removeEventListener('pointermove', this.dragging);
+ document.removeEventListener('pointerup', this.dragEnd);
+ document.addEventListener('pointermove', this.dragging);
+ document.addEventListener('pointerup', this.dragEnd);
this._isDragging = true;
this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
@@ -137,7 +132,7 @@ export default class MobileInkOverlay extends React.Component {
e.preventDefault();
e.stopPropagation();
- }
+ };
@action
dragging = (e: PointerEvent) => {
@@ -150,41 +145,39 @@ export default class MobileInkOverlay extends React.Component {
e.preventDefault();
e.stopPropagation();
- }
+ };
@action
dragEnd = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
+ document.removeEventListener('pointermove', this.dragging);
+ document.removeEventListener('pointerup', this.dragEnd);
this._isDragging = false;
e.preventDefault();
e.stopPropagation();
- }
+ };
render() {
-
return (
- <div className="mobileInkOverlay"
+ <div
+ className="mobileInkOverlay"
style={{
width: this._width,
height: this._height,
- position: "absolute",
+ position: 'absolute',
transform: `translate(${this._x}px, ${this._y}px)`,
zIndex: 30000,
- pointerEvents: "none",
- borderStyle: this._isDragging ? "solid" : "dashed",
- }
- }
- ref={this._mainCont}
- >
+ pointerEvents: 'none',
+ borderStyle: this._isDragging ? 'solid' : 'dashed',
+ }}
+ ref={this._mainCont}>
<p>{this._text}</p>
<div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
<div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
<div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
<div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
- </div >
+ </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index cfcc48608..f19496d25 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,57 +1,240 @@
-
import { library } from '@fortawesome/fontawesome-svg-core';
import {
- faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
- faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
- faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
- faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
- faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
- faThumbtack, faTree, faTv, faBook, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft,
- faAlignRight, faAlignLeft
+ faTasks,
+ faReply,
+ faQuoteLeft,
+ faHandPointLeft,
+ faFolderOpen,
+ faAngleDoubleLeft,
+ faExternalLinkSquareAlt,
+ faMobile,
+ faThLarge,
+ faWindowClose,
+ faEdit,
+ faTrashAlt,
+ faPalette,
+ faAngleRight,
+ faBell,
+ faTrash,
+ faCamera,
+ faExpand,
+ faCaretDown,
+ faCaretLeft,
+ faCaretRight,
+ faCaretSquareDown,
+ faCaretSquareRight,
+ faArrowsAltH,
+ faPlus,
+ faMinus,
+ faTerminal,
+ faToggleOn,
+ faFile as fileSolid,
+ faExternalLinkAlt,
+ faLocationArrow,
+ faSearch,
+ faFileDownload,
+ faStop,
+ faCalculator,
+ faWindowMaximize,
+ faAddressCard,
+ faQuestionCircle,
+ faArrowLeft,
+ faArrowRight,
+ faArrowDown,
+ faArrowUp,
+ faBolt,
+ faBullseye,
+ faCaretUp,
+ faCat,
+ faCheck,
+ faChevronRight,
+ faClipboard,
+ faClone,
+ faCloudUploadAlt,
+ faCommentAlt,
+ faCompressArrowsAlt,
+ faCut,
+ faEllipsisV,
+ faEraser,
+ faExclamation,
+ faFileAlt,
+ faFileAudio,
+ faFilePdf,
+ faFilm,
+ faFilter,
+ faFont,
+ faGlobeAsia,
+ faHighlighter,
+ faLongArrowAltRight,
+ faMicrophone,
+ faMousePointer,
+ faMusic,
+ faObjectGroup,
+ faPause,
+ faPen,
+ faPenNib,
+ faPhone,
+ faPlay,
+ faPortrait,
+ faRedoAlt,
+ faStamp,
+ faStickyNote,
+ faThumbtack,
+ faTree,
+ faTv,
+ faBook,
+ faUndoAlt,
+ faVideo,
+ faAsterisk,
+ faBrain,
+ faImage,
+ faPaintBrush,
+ faTimes,
+ faEye,
+ faHome,
+ faLongArrowAltLeft,
+ faBars,
+ faTh,
+ faChevronLeft,
+ faAlignRight,
+ faAlignLeft,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import * as React from "react";
+import * as React from 'react';
import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents';
-import { DocumentType } from "../client/documents/DocumentTypes";
+import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { SettingsManager, ColorScheme } from '../client/util/SettingsManager';
import { Transform } from '../client/util/Transform';
-import { UndoManager } from "../client/util/UndoManager";
+import { UndoManager } from '../client/util/UndoManager';
import { TabDocView } from '../client/views/collections/TabDocView';
-import { CollectionViewType } from "../client/views/collections/CollectionView";
-import { GestureOverlay } from "../client/views/GestureOverlay";
-import { AudioBox } from "../client/views/nodes/AudioBox";
+import { GestureOverlay } from '../client/views/GestureOverlay';
+import { AudioBox } from '../client/views/nodes/AudioBox';
import { DocumentView } from '../client/views/nodes/DocumentView';
-import { RichTextMenu } from "../client/views/nodes/formattedText/RichTextMenu";
-import { RadialMenu } from "../client/views/nodes/RadialMenu";
+import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu';
+import { RadialMenu } from '../client/views/nodes/RadialMenu';
import { Doc, DocListCast } from '../fields/Doc';
import { InkTool } from '../fields/InkField';
-import { List } from "../fields/List";
-import { ScriptField } from "../fields/ScriptField";
-import { Cast, FieldValue } from '../fields/Types';
+import { List } from '../fields/List';
+import { ScriptField } from '../fields/ScriptField';
+import { Cast, FieldValue, StrCast } from '../fields/Types';
import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
-import { AudioUpload } from "./AudioUpload";
-import { Uploader } from "./ImageUpload";
-import "./AudioUpload.scss";
-import "./ImageUpload.scss";
-import "./MobileInterface.scss";
-
-library.add(...[faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
- faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
- faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
- faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
- faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
- faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft,
- faAlignLeft, faAlignRight].map(m => m as any));
-
+import { AudioUpload } from './AudioUpload';
+import { Uploader } from './ImageUpload';
+import './AudioUpload.scss';
+import './ImageUpload.scss';
+import './MobileInterface.scss';
+
+library.add(
+ ...[
+ faTasks,
+ faReply,
+ faQuoteLeft,
+ faHandPointLeft,
+ faFolderOpen,
+ faAngleDoubleLeft,
+ faExternalLinkSquareAlt,
+ faMobile,
+ faThLarge,
+ faWindowClose,
+ faEdit,
+ faTrashAlt,
+ faPalette,
+ faAngleRight,
+ faBell,
+ faTrash,
+ faCamera,
+ faExpand,
+ faCaretDown,
+ faCaretLeft,
+ faCaretRight,
+ faCaretSquareDown,
+ faCaretSquareRight,
+ faArrowsAltH,
+ faPlus,
+ faMinus,
+ faTerminal,
+ faToggleOn,
+ fileSolid,
+ faExternalLinkAlt,
+ faLocationArrow,
+ faSearch,
+ faFileDownload,
+ faStop,
+ faCalculator,
+ faWindowMaximize,
+ faAddressCard,
+ faQuestionCircle,
+ faArrowLeft,
+ faArrowRight,
+ faArrowDown,
+ faArrowUp,
+ faBolt,
+ faBullseye,
+ faCaretUp,
+ faCat,
+ faCheck,
+ faChevronRight,
+ faClipboard,
+ faClone,
+ faCloudUploadAlt,
+ faCommentAlt,
+ faCompressArrowsAlt,
+ faCut,
+ faEllipsisV,
+ faEraser,
+ faExclamation,
+ faFileAlt,
+ faFileAudio,
+ faFilePdf,
+ faFilm,
+ faFilter,
+ faFont,
+ faGlobeAsia,
+ faHighlighter,
+ faLongArrowAltRight,
+ faMicrophone,
+ faMousePointer,
+ faMusic,
+ faObjectGroup,
+ faPause,
+ faPen,
+ faPenNib,
+ faPhone,
+ faPlay,
+ faPortrait,
+ faRedoAlt,
+ faStamp,
+ faStickyNote,
+ faThumbtack,
+ faTree,
+ faTv,
+ faUndoAlt,
+ faBook,
+ faVideo,
+ faAsterisk,
+ faBrain,
+ faImage,
+ faPaintBrush,
+ faTimes,
+ faEye,
+ faHome,
+ faLongArrowAltLeft,
+ faBars,
+ faTh,
+ faChevronLeft,
+ faAlignLeft,
+ faAlignRight,
+ ].map(m => m as any)
+);
@observer
export class MobileInterface extends React.Component {
static Instance: MobileInterface;
- private _library: Promise<Doc>;
+ private _library: Doc;
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
@@ -64,36 +247,38 @@ export class MobileInterface extends React.Component {
@observable private _homeDoc: Doc = this._mainDoc; // home menu as a document
@observable private _parents: Array<Doc> = []; // array of parent docs (for pathbar)
- @computed private get mainContainer() { return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
+ @computed private get mainContainer() {
+ return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : Doc.GuestMobile;
+ }
constructor(props: Readonly<{}>) {
super(props);
- this._library = CurrentUserUtils.setupLibrary(Doc.UserDoc()); // to access documents in Dash Web
+ this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), 'myDashboards'); // to access documents in Dash Web
MobileInterface.Instance = this;
}
@action
componentDidMount = () => {
// if the home menu is in list view -> adjust the menu toggle appropriately
- this._menuListView = this._homeDoc._viewType === "stacking" ? true : false;
- CurrentUserUtils.SelectedTool = InkTool.None; // ink should intially be set to none
+ this._menuListView = this._homeDoc._viewType === 'stacking' ? true : false;
+ Doc.ActiveTool = InkTool.None; // ink should intially be set to none
Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home
AudioBox.Enabled = true;
// remove double click to avoid mobile zoom in
- document.removeEventListener("dblclick", this.onReactDoubleClick);
- document.addEventListener("dblclick", this.onReactDoubleClick);
- }
+ document.removeEventListener('dblclick', this.onReactDoubleClick);
+ document.addEventListener('dblclick', this.onReactDoubleClick);
+ };
@action
componentWillUnmount = () => {
document.removeEventListener('dblclick', this.onReactDoubleClick);
- }
+ };
// Prevent zooming in when double tapping the screen
onReactDoubleClick = (e: MouseEvent) => {
e.stopPropagation();
- }
+ };
// Switch the mobile view to the given doc
@action
@@ -110,7 +295,7 @@ export class MobileInterface extends React.Component {
// Ensures that switching to home is not registed
UndoManager.undoStack.length = 0;
UndoManager.redoStack.length = 0;
- }
+ };
// For toggling the hamburger menu
@action
@@ -120,29 +305,29 @@ export class MobileInterface extends React.Component {
if (this._ink) {
this.onSwitchInking();
}
- }
+ };
/**
* Method called when 'Library' button is pressed on the home screen
*/
switchToLibrary = async () => {
- this._library.then(library => this.switchCurrentView(library));
- runInAction(() => this._homeMenu = false);
+ this.switchCurrentView(this._library);
+ runInAction(() => (this._homeMenu = false));
this.toggleSidebar();
- }
+ };
/**
* Back method for navigating through items
*/
@action
back = () => {
- const header = document.getElementById("header") as HTMLElement;
+ const header = document.getElementById('header') as HTMLElement;
const doc = Cast(this._parents.pop(), Doc) as Doc; // Parent document
// Case 1: Parent document is 'dashboards'
- if (doc === Cast(this._library, Doc) as Doc) {
+ if (doc === (Cast(this._library, Doc) as Doc)) {
this.dashboards = null;
- this._library.then(library => this.switchCurrentView(library));
+ this.switchCurrentView(this._library);
// Case 2: Parent document is the 'home' menu (root node)
- } else if (doc === Cast(this._homeDoc, Doc) as Doc) {
+ } else if (doc === (Cast(this._homeDoc, Doc) as Doc)) {
this._homeMenu = true;
this._parents = [];
this.dashboards = null;
@@ -155,7 +340,7 @@ export class MobileInterface extends React.Component {
header.textContent = String(doc.title);
}
this._ink = false; // turns ink off
- }
+ };
/**
* Return 'Home", which implies returning to 'Home' menu buttons
@@ -171,7 +356,7 @@ export class MobileInterface extends React.Component {
if (this._sidebarActive) {
this.toggleSidebar();
}
- }
+ };
/**
* Return to primary Dashboard in library (Dashboards Doc)
@@ -179,10 +364,10 @@ export class MobileInterface extends React.Component {
@action
returnMain = () => {
this._parents = [this._homeDoc];
- this._library.then(library => this.switchCurrentView(library));
+ this.switchCurrentView(this._library);
this._homeMenu = false;
this.dashboards = null;
- }
+ };
/**
* Note: window.innerWidth and window.screen.width compute different values.
@@ -190,14 +375,14 @@ export class MobileInterface extends React.Component {
* display resolution which computes differently.
*/
returnWidth = () => window.innerWidth; //The windows width
- returnHeight = () => (window.innerHeight - 300); //Calculating the windows height (-300 to account for topbar)
- whitebackground = () => "white";
+ returnHeight = () => window.innerHeight - 300; //Calculating the windows height (-300 to account for topbar)
+ whitebackground = () => 'white';
/**
* DocumentView for graphic display of all documents
*/
@computed get displayDashboards() {
- return !this.mainContainer ? (null) :
- <div style={{ position: "relative", top: '198px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}>
+ return !this.mainContainer ? null : (
+ <div style={{ position: 'relative', top: '198px', height: `calc(100% - 350px)`, width: '100%', left: '0%' }}>
<DocumentView
Document={this.mainContainer}
DataDoc={undefined}
@@ -214,7 +399,6 @@ export class MobileInterface extends React.Component {
isContentActive={emptyFunction}
focus={DocUtils.DefaultFocus}
styleProvider={this.whitebackground}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -224,7 +408,8 @@ export class MobileInterface extends React.Component {
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
/>
- </div>;
+ </div>
+ );
}
/**
@@ -234,20 +419,19 @@ export class MobileInterface extends React.Component {
*/
handleClick = async (doc: Doc) => {
runInAction(() => {
- if (doc.type !== "collection" && this._sidebarActive) {
+ if (doc.type !== 'collection' && this._sidebarActive) {
this._parents.push(this._activeDoc);
this.switchCurrentView(doc);
this._homeMenu = false;
this.toggleSidebar();
- }
- else {
+ } else {
this._parents.push(this._activeDoc);
this.switchCurrentView(doc);
this._homeMenu = false;
this.dashboards = doc;
}
});
- }
+ };
/**
* Called when an item in the library is clicked and should
@@ -261,30 +445,31 @@ export class MobileInterface extends React.Component {
this._homeMenu = false;
this.dashboards = doc;
this.toggleSidebar();
- }
+ };
// Renders the graphical pathbar
renderPathbar = () => {
const docPath = [...this._parents, this._activeDoc];
- const items = docPath.map((doc: Doc, index: any) =>
+ const items = docPath.map((doc: Doc, index: any) => (
<div className="pathbarItem" key={index}>
- {index === 0 ? (null) : <FontAwesomeIcon key="icon" className="pathIcon" icon="angle-right" size="lg" />}
- <div className="pathbarText"
- style={{ backgroundColor: this._homeMenu || doc === this._activeDoc ? "rgb(119,17,37)" : undefined }}
- onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+ {index === 0 ? null : <FontAwesomeIcon key="icon" className="pathIcon" icon="angle-right" size="lg" />}
+ <div className="pathbarText" style={{ backgroundColor: this._homeMenu || doc === this._activeDoc ? 'rgb(119,17,37)' : undefined }} onClick={() => this.handlePathClick(doc, index)}>
+ {StrCast(doc.title)}
</div>
- </div>);
- return (<div className="pathbar">
- <div className="scrollmenu">
- {items}
</div>
- {!this._parents.length ? (null) :
- <div className="back" >
- <FontAwesomeIcon onClick={this.back} icon={"chevron-left"} color="white" size={"2x"} />
- </div>}
- <div className="hidePath" />
- </div>);
- }
+ ));
+ return (
+ <div className="pathbar">
+ <div className="scrollmenu">{items}</div>
+ {!this._parents.length ? null : (
+ <div className="back">
+ <FontAwesomeIcon onClick={this.back} icon={'chevron-left'} color="white" size={'2x'} />
+ </div>
+ )}
+ <div className="hidePath" />
+ </div>
+ );
+ };
// Handles when user clicks on a document in the pathbar
@action
@@ -301,7 +486,7 @@ export class MobileInterface extends React.Component {
this.switchCurrentView(doc);
this._parents.length = index;
}
- }
+ };
// Renders the contents of the menu and sidebar
@computed get renderDefaultContent() {
@@ -310,7 +495,9 @@ export class MobileInterface extends React.Component {
<div>
<div className="navbar">
<FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
- <div className="header" id="header">{this._homeDoc.title}</div>
+ <div className="header" id="header">
+ {StrCast(this._homeDoc.title)}
+ </div>
<div className="cover" id="cover" onClick={e => e.stopPropagation()}></div>
<div className="toggle-btn" id="menuButton" onClick={this.toggleSidebar}>
<span></span>
@@ -323,20 +510,23 @@ export class MobileInterface extends React.Component {
);
}
// stores dashboards documents as 'dashboards' variable
- let dashboards = CurrentUserUtils.MyDashboards;
+ let dashboards = Doc.MyDashboards;
if (this.dashboards) {
dashboards = this.dashboards;
}
// returns a list of navbar buttons as 'buttons'
const buttons = DocListCast(dashboards.data).map((doc: Doc, index: any) => {
- if (doc.type !== "ink") {
+ if (doc.type !== 'ink') {
return (
- <div
- className="item"
- key={index}>
- <div className="item-title" onClick={() => this.handleClick(doc)}> {doc.title} </div>
- <div className="item-type" onClick={() => this.handleClick(doc)}>{doc.type}</div>
- <FontAwesomeIcon onClick={() => this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === "collection" ? "block" : "none"}` }} />
+ <div className="item" key={index}>
+ <div className="item-title" onClick={() => this.handleClick(doc)}>
+ {' '}
+ {doc.title as string}{' '}
+ </div>
+ <div className="item-type" onClick={() => this.handleClick(doc)}>
+ {doc.type as string}
+ </div>
+ <FontAwesomeIcon onClick={() => this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === 'collection' ? 'block' : 'none'}` }} />
<FontAwesomeIcon className="open" onClick={() => this.openFromSidebar(doc)} icon="external-link-alt" size="lg" />
</div>
);
@@ -347,43 +537,39 @@ export class MobileInterface extends React.Component {
<div>
<div className="navbar">
<FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
- <div className="header" id="header">{this._sidebarActive ? "library" : this._activeDoc.title}</div>
- <div className={`toggle-btn ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
+ <div className="header" id="header">
+ {this._sidebarActive ? 'library' : (this._activeDoc.title as string)}
+ </div>
+ <div className={`toggle-btn ${this._sidebarActive ? 'active' : ''}`} onClick={this.toggleSidebar}>
<span></span>
<span></span>
<span></span>
</div>
- <div className={`background ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}></div>
+ <div className={`background ${this._sidebarActive ? 'active' : ''}`} onClick={this.toggleSidebar}></div>
</div>
{this.renderPathbar()}
- <div className={`sidebar ${this._sidebarActive ? "active" : ""}`}>
+ <div className={`sidebar ${this._sidebarActive ? 'active' : ''}`}>
<div className="sidebarButtons">
- {this.dashboards ?
+ {this.dashboards ? (
<>
{buttons}
- <div
- className="item" key="home"
- onClick={this.returnMain}
- style={{ opacity: 0.7 }}>
+ <div className="item" key="home" onClick={this.returnMain} style={{ opacity: 0.7 }}>
<FontAwesomeIcon className="right" icon="angle-double-left" size="lg" />
<div className="item-type">Return to dashboards</div>
</div>
- </> :
+ </>
+ ) : (
<>
{buttons}
- <div
- className="item"
- style={{ opacity: 0.7 }}
- onClick={() => this.createNewDashboard()}>
+ <div className="item" style={{ opacity: 0.7 }} onClick={() => this.createNewDashboard()}>
<FontAwesomeIcon className="right" icon="plus" size="lg" />
<div className="item-type">Create New Dashboard</div>
</div>
</>
- }
+ )}
</div>
</div>
- <div className={`blanket ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
- </div>
+ <div className={`blanket ${this._sidebarActive ? 'active' : ''}`} onClick={this.toggleSidebar}></div>
</div>
);
}
@@ -392,57 +578,54 @@ export class MobileInterface extends React.Component {
* Handles the 'Create New Dashboard' button in the menu (taken from MainView.tsx)
*/
@action
- createNewDashboard = async (id?: string) => {
- const scens = CurrentUserUtils.MyDashboards;
+ createNewDashboard = (id?: string) => {
+ const scens = Doc.MyDashboards;
const dashboardCount = DocListCast(scens.data).length + 1;
const freeformOptions: DocumentOptions = {
x: 0,
y: 400,
- title: "Collection " + dashboardCount,
+ title: 'Collection ' + dashboardCount,
};
- const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
+ const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
+ const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row');
const toggleTheme = ScriptField.MakeScript(`self.colorScheme = self.colorScheme ? undefined: ${ColorScheme.Dark}}`);
const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`);
dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneDashboard!]);
- dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Dashboard Layout"]);
+ dashboardDoc.contextMenuLabels = new List<string>(['Toggle Theme Colors', 'Toggle Comic Mode', 'New Dashboard Layout']);
- Doc.AddDocToList(scens, "data", dashboardDoc);
- }
+ Doc.AddDocToList(scens, 'data', dashboardDoc);
+ };
// Button for switching between pen and ink mode
@action
onSwitchInking = () => {
- const button = document.getElementById("inkButton") as HTMLElement;
- button.style.backgroundColor = this._ink ? "white" : "black";
- button.style.color = this._ink ? "black" : "white";
+ const button = document.getElementById('inkButton') as HTMLElement;
+ button.style.backgroundColor = this._ink ? 'white' : 'black';
+ button.style.color = this._ink ? 'black' : 'white';
if (!this._ink) {
- CurrentUserUtils.SelectedTool = InkTool.Pen;
+ Doc.ActiveTool = InkTool.Pen;
this._ink = true;
} else {
- CurrentUserUtils.SelectedTool = InkTool.None;
+ Doc.ActiveTool = InkTool.None;
this._ink = false;
}
- }
+ };
// The static ink menu that appears at the top
@computed get inkMenu() {
- return this._activeDoc._viewType !== CollectionViewType.Docking || !this._ink ? (null) :
- <div className="colorSelector">
- {/* <CollectionFreeFormViewChrome /> */}
- </div>;
+ return this._activeDoc._viewType !== CollectionViewType.Docking || !this._ink ? null : <div className="colorSelector">{/* <CollectionFreeFormViewChrome /> */}</div>;
}
// DocButton that uses UndoManager and handles the opacity change if CanUndo is true
@computed get undo() {
- if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
- this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== "WORKSPACES") {
+ if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') {
return (
- <div className="docButton"
- style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }}
+ <div
+ className="docButton"
+ style={{ backgroundColor: 'black', color: 'white', fontSize: '60', opacity: UndoManager.CanUndo() ? '1' : '0.4' }}
id="undoButton"
title="undo"
onClick={(e: React.MouseEvent) => {
@@ -450,17 +633,18 @@ export class MobileInterface extends React.Component {
e.stopPropagation();
}}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="undo-alt" />
- </div>);
- } else return (null);
+ </div>
+ );
+ } else return null;
}
// DocButton that uses UndoManager and handles the opacity change if CanRedo is true
@computed get redo() {
- if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
- this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== "WORKSPACES") {
+ if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') {
return (
- <div className="docButton"
- style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }}
+ <div
+ className="docButton"
+ style={{ backgroundColor: 'black', color: 'white', fontSize: '60', opacity: UndoManager.CanRedo() ? '1' : '0.4' }}
id="undoButton"
title="redo"
onClick={(e: React.MouseEvent) => {
@@ -468,78 +652,81 @@ export class MobileInterface extends React.Component {
e.stopPropagation();
}}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="redo-alt" />
- </div>);
- } else return (null);
+ </div>
+ );
+ } else return null;
}
// DocButton for switching into ink mode
@computed get drawInk() {
- return !this.mainContainer || this._activeDoc._viewType !== CollectionViewType.Docking ? (null) :
- <div className="docButton"
- id="inkButton"
- title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
- onClick={this.onSwitchInking}>
+ return !this.mainContainer || this._activeDoc._viewType !== CollectionViewType.Docking ? null : (
+ <div className="docButton" id="inkButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.onSwitchInking}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib" />
- </div>;
+ </div>
+ );
}
// DocButton: Button that appears on the bottom of the screen to initiate image upload
@computed get uploadImageButton() {
- if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== CollectionViewType.Docking && this._activeDoc.title !== "WORKSPACES") {
- return <div className="docButton"
- id="imageButton"
- title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
- onClick={this.toggleUpload}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" />
- </div>;
- } else return (null);
+ if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== CollectionViewType.Docking && this._activeDoc.title !== 'WORKSPACES') {
+ return (
+ <div className="docButton" id="imageButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.toggleUpload}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" />
+ </div>
+ );
+ } else return null;
}
// DocButton to download images on the mobile
@computed get downloadDocument() {
- if (this._activeDoc.type === "image" || this._activeDoc.type === "pdf" || this._activeDoc.type === "video") {
- return <div className="docButton"
- title={"Download Image"}
- style={{ backgroundColor: "white", color: "black" }}
- onClick={e => window.open(this._activeDoc["data-path"]?.toString())}> {/* daa-path holds the url */}
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download" />
- </div>;
- } else return (null);
+ if (this._activeDoc.type === 'image' || this._activeDoc.type === 'pdf' || this._activeDoc.type === 'video') {
+ return (
+ <div className="docButton" title={'Download Image'} style={{ backgroundColor: 'white', color: 'black' }} onClick={e => window.open(this._activeDoc['data-path']?.toString())}>
+ {' '}
+ {/* daa-path holds the url */}
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download" />
+ </div>
+ );
+ } else return null;
}
// DocButton for pinning images to presentation
@computed get pinToPresentation() {
// Only making button available if it is an image
- if (!(this._activeDoc.type === "collection" || this._activeDoc.type === "presentation")) {
+ if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) {
const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc);
- return <div className="docButton"
- title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
- style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
- onClick={e => TabDocView.PinDoc(this._activeDoc, { unpin: isPinned })}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
- </div>;
- } else return (null);
+ return (
+ <div
+ className="docButton"
+ title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'}
+ style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }}
+ onClick={e => TabDocView.PinDoc(this._activeDoc)}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
+ </div>
+ );
+ } else return null;
}
// Buttons for switching the menu between large and small icons
@computed get switchMenuView() {
- return this._activeDoc.title !== this._homeDoc.title ? (null) :
+ return this._activeDoc.title !== this._homeDoc.title ? null : (
<div className="homeSwitch">
- <div className={`list ${!this._menuListView ? "active" : ""}`} onClick={this.changeToIconView}>
+ <div className={`list ${!this._menuListView ? 'active' : ''}`} onClick={this.changeToIconView}>
<FontAwesomeIcon size="sm" icon="th-large" />
</div>
- <div className={`list ${this._menuListView ? "active" : ""}`} onClick={this.changeToListView}>
+ <div className={`list ${this._menuListView ? 'active' : ''}`} onClick={this.changeToListView}>
<FontAwesomeIcon size="sm" icon="bars" />
</div>
- </div>;
+ </div>
+ );
}
// Logic for switching the menu into the icons
@action
changeToIconView = () => {
- if (this._homeDoc._viewType = "stacking") {
+ if ((this._homeDoc._viewType = 'stacking')) {
this._menuListView = false;
- this._homeDoc._viewType = "masonry";
+ this._homeDoc._viewType = 'masonry';
this._homeDoc.columnWidth = 300;
this._homeDoc._columnWidth = 300;
const menuButtons = DocListCast(this._homeDoc.data);
@@ -552,13 +739,13 @@ export class MobileInterface extends React.Component {
doc._nativeWidth = 400;
});
}
- }
+ };
// Logic for switching the menu into the stacking view
@action
changeToListView = () => {
- if (this._homeDoc._viewType = "masonry") {
- this._homeDoc._viewType = "stacking";
+ if ((this._homeDoc._viewType = 'masonry')) {
+ this._homeDoc._viewType = 'stacking';
this._menuListView = true;
const menuButtons = DocListCast(this._homeDoc.data);
menuButtons.map(doc => {
@@ -569,60 +756,59 @@ export class MobileInterface extends React.Component {
doc._nativeWidth = 900;
});
}
- }
+ };
// For setting up the presentation document for the home menu
@action
setupDefaultPresentation = () => {
- const presentation = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+ const presentation = Doc.ActivePresentation;
if (presentation) {
this.switchCurrentView(presentation);
this._homeMenu = false;
}
- }
+ };
// For toggling image upload pop up
@action
- toggleUpload = () => this._imageUploadActive = !this._imageUploadActive
+ toggleUpload = () => (this._imageUploadActive = !this._imageUploadActive);
// For toggling audio record and dictate pop up
@action
- toggleAudio = () => this._audioUploadActive = !this._audioUploadActive
+ toggleAudio = () => (this._audioUploadActive = !this._audioUploadActive);
// Button for toggling the upload pop up in a collection
@action
toggleUploadInCollection = () => {
- const button = document.getElementById("imageButton") as HTMLElement;
- button.style.backgroundColor = this._imageUploadActive ? "white" : "black";
- button.style.color = this._imageUploadActive ? "black" : "white";
+ const button = document.getElementById('imageButton') as HTMLElement;
+ button.style.backgroundColor = this._imageUploadActive ? 'white' : 'black';
+ button.style.color = this._imageUploadActive ? 'black' : 'white';
this._imageUploadActive = !this._imageUploadActive;
- }
+ };
// For closing the image upload pop up
@action
closeUpload = () => {
this._imageUploadActive = false;
- }
+ };
// Returns the image upload pop up
@computed get uploadImage() {
- const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.SharingDoc(), Doc) as Doc;
+ const doc = !this._homeMenu ? this._activeDoc : (Cast(Doc.SharingDoc(), Doc) as Doc);
return <Uploader Document={doc} />;
}
- // Radial menu can only be used if it is a colleciton and it is not a homeDoc
+ // Radial menu can only be used if it is a colleciton and it is not a homeDoc
// (and cannot be used on Dashboard to avoid pin to presentation opening on right)
@computed get displayRadialMenu() {
- return this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
- this._activeDoc._viewType !== CollectionViewType.Docking ? <RadialMenu /> : (null);
+ return this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== CollectionViewType.Docking ? <RadialMenu /> : null;
}
onDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
- }
+ };
/**
* MENU BUTTON
@@ -634,22 +820,22 @@ export class MobileInterface extends React.Component {
const mobileUpload = Cast(Doc.SharingDoc(), Doc) as Doc;
this.switchCurrentView(mobileUpload);
this._homeMenu = false;
- }
+ };
render() {
return (
<div className="mobileInterface-container" onDragOver={this.onDragOver}>
<SettingsManager />
- <div className={`image-upload ${this._imageUploadActive ? "active" : ""}`}>
- {this.uploadImage}
- </div>
- <div className={`audio-upload ${this._audioUploadActive ? "active" : ""}`}>
+ <div className={`image-upload ${this._imageUploadActive ? 'active' : ''}`}>{this.uploadImage}</div>
+ <div className={`audio-upload ${this._audioUploadActive ? 'active' : ''}`}>
<AudioUpload />
</div>
{this.switchMenuView}
{this.inkMenu}
<GestureOverlay>
- <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
+ <div style={{ display: 'none' }}>
+ <RichTextMenu key="rich" />
+ </div>
<div className="docButtonContainer">
{this.pinToPresentation}
{this.downloadDocument}
@@ -667,21 +853,31 @@ export class MobileInterface extends React.Component {
}
}
-
//Global functions for mobile menu
-ScriptingGlobals.add(function switchToMobileLibrary() { return MobileInterface.Instance.switchToLibrary(); },
- "opens the library to navigate through dashboards on Dash Mobile");
-ScriptingGlobals.add(function openMobileUploads() { return MobileInterface.Instance.toggleUpload(); },
- "opens the upload files menu for Dash Mobile");
-ScriptingGlobals.add(function switchToMobileUploadCollection() { return MobileInterface.Instance.switchToMobileUploads(); },
- "opens the mobile uploads collection on Dash Mobile");
-ScriptingGlobals.add(function openMobileAudio() { return MobileInterface.Instance.toggleAudio(); },
- "opens the record and dictate menu on Dash Mobile");
-ScriptingGlobals.add(function switchToMobilePresentation() { return MobileInterface.Instance.setupDefaultPresentation(); },
- "opens the presentation on Dash Mobile");
-ScriptingGlobals.add(function openMobileSettings() { return SettingsManager.Instance.open(); },
- "opens settings on Dash Mobile");
+ScriptingGlobals.add(function switchToMobileLibrary() {
+ return MobileInterface.Instance.switchToLibrary();
+}, 'opens the library to navigate through dashboards on Dash Mobile');
+ScriptingGlobals.add(function openMobileUploads() {
+ return MobileInterface.Instance.toggleUpload();
+}, 'opens the upload files menu for Dash Mobile');
+ScriptingGlobals.add(function switchToMobileUploadCollection() {
+ return MobileInterface.Instance.switchToMobileUploads();
+}, 'opens the mobile uploads collection on Dash Mobile');
+ScriptingGlobals.add(function openMobileAudio() {
+ return MobileInterface.Instance.toggleAudio();
+}, 'opens the record and dictate menu on Dash Mobile');
+ScriptingGlobals.add(function switchToMobilePresentation() {
+ return MobileInterface.Instance.setupDefaultPresentation();
+}, 'opens the presentation on Dash Mobile');
+ScriptingGlobals.add(function openMobileSettings() {
+ return SettingsManager.Instance.open();
+}, 'opens settings on Dash Mobile');
// Other global functions for mobile
-ScriptingGlobals.add(function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); },
- "changes the active document displayed on the Dash Mobile", "(doc: any)"); \ No newline at end of file
+ScriptingGlobals.add(
+ function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) {
+ return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch);
+ },
+ 'changes the active document displayed on the Dash Mobile',
+ '(doc: any)'
+);
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
index 4a1e26078..6cbf86f77 100644
--- a/src/mobile/MobileMain.tsx
+++ b/src/mobile/MobileMain.tsx
@@ -1,25 +1,26 @@
-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";
+import * as ReactDOM from 'react-dom';
+import { DocServer } from '../client/DocServer';
+import { Docs } from '../client/documents/Documents';
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
+import { AssignAllExtensions } from '../extensions/General/Extensions';
+import { MobileInterface } from './MobileInterface';
AssignAllExtensions();
(async () => {
const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + " (mobile)");
+ 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.id);
- }
- document.getElementById('root')!.addEventListener('wheel', event => {
- if (event.ctrlKey) {
- event.preventDefault();
- }
- }, true);
+ await CurrentUserUtils.loadUserDocument(info.id);
+ 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/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index bfa07d47a..787e331c5 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -1,31 +1,30 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _success } from "../RouteManager";
+import ApiManager, { Registration } from './ApiManager';
+import { Method, _success } from '../RouteManager';
import * as formidable from 'formidable';
import v4 = require('uuid/v4');
const AdmZip = require('adm-zip');
import { extname, basename, dirname } from 'path';
-import { createReadStream, createWriteStream, unlink, writeFile } from "fs";
-import { publicDirectory, filesDirectory } from "..";
-import { Database } from "../database";
-import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils";
+import { createReadStream, createWriteStream, unlink, writeFile } from 'fs';
+import { publicDirectory, filesDirectory } from '..';
+import { Database } from '../database';
+import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils';
import * as sharp from 'sharp';
-import { AcceptableMedia, Upload } from "../SharedMediaTypes";
-import { normalize } from "path";
-import RouteSubscriber from "../RouteSubscriber";
+import { AcceptableMedia, Upload } from '../SharedMediaTypes';
+import { normalize } from 'path';
+import RouteSubscriber from '../RouteSubscriber';
const imageDataUri = require('image-data-uri');
-import { isWebUri } from "valid-url";
-import { Opt } from "../../fields/Doc";
-import { SolrManager } from "./SearchManager";
-import { StringDecoder } from "string_decoder";
+import { SolrManager } from './SearchManager';
+const fs = require('fs');
export enum Directory {
- parsed_files = "parsed_files",
- images = "images",
- videos = "videos",
- pdfs = "pdfs",
- text = "text",
- pdf_thumbnails = "pdf_thumbnails",
- audio = "audio",
+ parsed_files = 'parsed_files',
+ images = 'images',
+ videos = 'videos',
+ pdfs = 'pdfs',
+ text = 'text',
+ pdf_thumbnails = 'pdf_thumbnails',
+ audio = 'audio',
+ csv = 'csv',
}
export function serverPathToFile(directory: Directory, filename: string) {
@@ -41,12 +40,19 @@ export function clientPathToFile(directory: Directory, filename: string) {
}
export default class UploadManager extends ApiManager {
-
protected initialize(register: Registration): void {
+ register({
+ method: Method.POST,
+ subscription: '/concatVideos',
+ secureHandler: async ({ req, res }) => {
+ // req.body contains the array of server paths to the videos
+ _success(res, await DashUploadUtils.concatVideos(req.body));
+ },
+ });
register({
method: Method.POST,
- subscription: "/uploadFormData",
+ subscription: '/uploadFormData',
secureHandler: async ({ req, res }) => {
const form = new formidable.IncomingForm();
form.keepExtensions = true;
@@ -65,16 +71,16 @@ export default class UploadManager extends ApiManager {
resolve();
});
});
- }
+ },
});
register({
method: Method.POST,
- subscription: "/uploadYoutubeVideo",
+ subscription: '/uploadYoutubeVideo',
secureHandler: async ({ req, res }) => {
//req.readableBuffer.head.data
return new Promise<void>(async resolve => {
- req.addListener("data", async (args) => {
+ req.addListener('data', async args => {
console.log(args);
const payload = String.fromCharCode.apply(String, args);
const videoId = JSON.parse(payload).videoId;
@@ -85,12 +91,12 @@ export default class UploadManager extends ApiManager {
resolve();
});
});
- }
+ },
});
register({
method: Method.POST,
- subscription: new RouteSubscriber("youtubeScreenshot"),
+ subscription: new RouteSubscriber('youtubeScreenshot'),
secureHandler: async ({ req, res }) => {
const { id, timecode } = req.body;
const convert = (raw: string) => {
@@ -115,34 +121,32 @@ export default class UploadManager extends ApiManager {
await DashUploadUtils.outputResizedImages(() => createReadStream(resolvedPath), resolvedName, pathToDirectory(Directory.images));
res.send({
accessPaths: {
- agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName)
- }
+ agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName),
+ },
} as Upload.FileInformation);
resolve();
});
});
- }
+ },
});
register({
method: Method.POST,
- subscription: "/uploadRemoteImage",
+ subscription: '/uploadRemoteImage',
secureHandler: async ({ req, res }) => {
-
const { sources } = req.body;
if (Array.isArray(sources)) {
const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)));
return res.send(results);
}
res.send();
- }
+ },
});
register({
method: Method.POST,
- subscription: "/uploadDoc",
+ subscription: '/uploadDoc',
secureHandler: ({ req, res }) => {
-
const form = new formidable.IncomingForm();
form.keepExtensions = true;
// let path = req.body.path;
@@ -150,11 +154,11 @@ export default class UploadManager extends ApiManager {
let remap = true;
const getId = (id: string): string => {
if (!remap) return id;
- if (id.endsWith("Proto")) return id;
+ if (id.endsWith('Proto')) return id;
if (id in ids) {
return ids[id];
} else {
- return ids[id] = v4();
+ return (ids[id] = v4());
}
};
const mapFn = (doc: any) => {
@@ -162,26 +166,30 @@ export default class UploadManager extends ApiManager {
doc.id = getId(doc.id);
}
for (const key in doc.fields) {
- if (!doc.fields.hasOwnProperty(key)) { continue; }
+ if (!doc.fields.hasOwnProperty(key)) {
+ continue;
+ }
const field = doc.fields[key];
- if (field === undefined || field === null) { continue; }
+ if (field === undefined || field === null) {
+ continue;
+ }
- if (field.__type === "Doc") {
+ if (field.__type === 'Doc') {
mapFn(field);
- } else if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
+ } else if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') {
field.fieldId = getId(field.fieldId);
- } else if (field.__type === "script" || field.__type === "computed") {
+ } else if (field.__type === 'script' || field.__type === 'computed') {
if (field.captures) {
field.captures.fieldId = getId(field.captures.fieldId);
}
- } else if (field.__type === "list") {
+ } else if (field.__type === 'list') {
mapFn(field);
- } else if (typeof field === "string") {
+ } else if (typeof field === 'string') {
const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g;
doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => {
return `${p1}${getId(p2)}"`;
});
- } else if (field.__type === "RichTextField") {
+ } else if (field.__type === 'RichTextField') {
const re = /("href"\s*:\s*")(.*?)"/g;
field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => {
return `${p1}${getId(p2)}"`;
@@ -191,87 +199,110 @@ export default class UploadManager extends ApiManager {
};
return new Promise<void>(resolve => {
form.parse(req, async (_err, fields, files) => {
- remap = fields.remap !== "false";
- let id: string = "";
+ remap = fields.remap !== 'false';
+ let id: string = '';
try {
for (const name in files) {
const f = files[name];
- const path_2 = Array.isArray(f) ? "" : f.path;
+ const path_2 = Array.isArray(f) ? '' : f.path;
const zip = new AdmZip(path_2);
zip.getEntries().forEach((entry: any) => {
- if (!entry.entryName.startsWith("files/")) return;
- let directory = dirname(entry.entryName) + "/";
+ if (!entry.entryName.startsWith('files/')) return;
+ let directory = dirname(entry.entryName) + '/';
const extension = extname(entry.entryName);
- const base = basename(entry.entryName).split(".")[0];
+ const base = basename(entry.entryName).split('.')[0];
try {
zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
- directory = "/" + directory;
+ directory = '/' + directory;
- createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_o" + extension));
- createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_s" + extension));
- createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_m" + extension));
- createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_l" + extension));
+ createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_o' + extension));
+ createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_s' + extension));
+ createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_m' + extension));
+ createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_l' + extension));
} catch (e) {
console.log(e);
}
});
- const json = zip.getEntry("doc.json");
+ const json = zip.getEntry('doc.json');
try {
- const data = JSON.parse(json.getData().toString("utf8"));
+ const data = JSON.parse(json.getData().toString('utf8'));
const datadocs = data.docs;
id = getId(data.id);
const docs = Object.keys(datadocs).map(key => datadocs[key]);
docs.forEach(mapFn);
- await Promise.all(docs.map((doc: any) => new Promise<void>(res => {
- Database.Instance.replace(doc.id, doc, (err, r) => {
- err && console.log(err);
- res();
- }, true);
- })));
- } catch (e) { console.log(e); }
- unlink(path_2, () => { });
+ await Promise.all(
+ docs.map(
+ (doc: any) =>
+ new Promise<void>(res => {
+ Database.Instance.replace(
+ doc.id,
+ doc,
+ (err, r) => {
+ err && console.log(err);
+ res();
+ },
+ true
+ );
+ })
+ )
+ );
+ } catch (e) {
+ console.log(e);
+ }
+ unlink(path_2, () => {});
}
SolrManager.update();
- res.send(JSON.stringify(id || "error"));
- } catch (e) { console.log(e); }
+ res.send(JSON.stringify(id || 'error'));
+ } catch (e) {
+ console.log(e);
+ }
resolve();
});
});
- }
+ },
});
register({
method: Method.POST,
- subscription: "/inspectImage",
+ subscription: '/inspectImage',
secureHandler: async ({ req, res }) => {
-
const { source } = req.body;
- if (typeof source === "string") {
+ if (typeof source === 'string') {
return res.send(await DashUploadUtils.InspectImage(source));
}
res.send({});
- }
+ },
});
register({
method: Method.POST,
- subscription: "/uploadURI",
+ subscription: '/uploadURI',
secureHandler: ({ req, res }) => {
const uri = req.body.uri;
const filename = req.body.name;
const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
+ const deleteFiles = req.body.replaceRootFilename;
if (!uri || !filename) {
- res.status(401).send("incorrect parameters specified");
+ res.status(401).send('incorrect parameters specified');
return;
}
+ if (deleteFiles) {
+ const path = serverPathToFile(Directory.images, '');
+ const regex = new RegExp(`${deleteFiles}.*`);
+ fs.readdirSync(path)
+ .filter((f: any) => regex.test(f))
+ .map((f: any) => fs.unlinkSync(path + f));
+ }
return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
const ext = extname(savedName).toLowerCase();
const { pngs, jpgs } = AcceptableMedia;
- const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" }] : [
- { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" },
- { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" },
- { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: "_l" },
- ];
+ const resizers = !origSuffix
+ ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }]
+ : [
+ { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Small },
+ { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium },
+ { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Large },
+ ];
let isImage = false;
if (pngs.includes(ext)) {
resizers.forEach(element => {
@@ -286,18 +317,19 @@ export default class UploadManager extends ApiManager {
}
if (isImage) {
resizers.forEach(resizer => {
- const path = serverPathToFile(Directory.images, filename + resizer.suffix + ext);
- createReadStream(savedName).pipe(resizer.resizer).pipe(createWriteStream(path));
+ const path = serverPathToFile(Directory.images, InjectSize(filename, resizer.suffix) + ext);
+ createReadStream(savedName)
+ .on('error', e => console.log('Resizing read:' + e))
+ .pipe(resizer.resizer)
+ .on('error', e => console.log('Resizing write: ' + e))
+ .pipe(createWriteStream(path).on('error', e => console.log('Resizing write: ' + e)));
});
-
}
res.send(clientPathToFile(Directory.images, filename + ext));
});
- }
+ },
});
-
}
-
}
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
@@ -305,7 +337,7 @@ function delay(ms: number) {
/**
* On success, returns a buffer containing the bytes of a screenshot
* of the video (optionally, at a timecode) specified by @param targetUrl.
- *
+ *
* On failure, returns undefined.
*/
async function captureYoutubeScreenshot(targetUrl: string) {
@@ -330,4 +362,4 @@ async function captureYoutubeScreenshot(targetUrl: string) {
// return buffer;
return null;
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 7be8a1e9f..53e55c1c3 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,10 +1,10 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import { Database } from "../database";
-import { msToTime } from "../ActionUtilities";
-import * as bcrypt from "bcrypt-nodejs";
-import { Opt } from "../../fields/Doc";
-import { WebSocket } from "../websocket";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import { Database } from '../database';
+import { msToTime } from '../ActionUtilities';
+import * as bcrypt from 'bcrypt-nodejs';
+import { Opt } from '../../fields/Doc';
+import { WebSocket } from '../websocket';
export const timeMap: { [id: string]: number } = {};
interface ActivityUnit {
@@ -13,28 +13,26 @@ interface ActivityUnit {
}
export default class UserManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
register({
method: Method.GET,
- subscription: "/getUsers",
+ subscription: '/getUsers',
secureHandler: async ({ res }) => {
- const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, "users");
+ const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, 'users');
const results = await cursor.toArray();
res.send(results.map((user: any) => ({ email: user.email, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })));
- }
+ },
});
register({
method: Method.POST,
- subscription: "/setCacheDocumentIds",
+ subscription: '/setCacheDocumentIds',
secureHandler: async ({ user, req, res }) => {
const result: any = {};
user.cacheDocumentIds = req.body.cacheDocumentIds;
user.save(err => {
if (err) {
- result.error = [{ msg: "Error while caching documents" }];
+ result.error = [{ msg: 'Error while caching documents' }];
}
});
@@ -42,32 +40,35 @@ export default class UserManager extends ApiManager {
// console.log(e);
// });
res.send(result);
- }
+ },
});
register({
method: Method.GET,
- subscription: "/getUserDocumentIds",
- secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId })
+ subscription: '/getUserDocumentIds',
+ secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }),
+ publicHandler: ({ res }) => res.send({ userDocumentId: '__guest__', linkDatabaseId: 3, sharingDocumentId: 2 }),
});
register({
method: Method.GET,
- subscription: "/getSharingDocumentId",
- secureHandler: ({ res, user }) => res.send(user.sharingDocumentId)
+ subscription: '/getSharingDocumentId',
+ secureHandler: ({ res, user }) => res.send(user.sharingDocumentId),
+ publicHandler: ({ res }) => res.send(2),
});
register({
method: Method.GET,
- subscription: "/getLinkDatabaseId",
- secureHandler: ({ res, user }) => res.send(user.linkDatabaseId)
+ subscription: '/getLinkDatabaseId',
+ secureHandler: ({ res, user }) => res.send(user.linkDatabaseId),
+ publicHandler: ({ res }) => res.send(3),
});
register({
method: Method.GET,
- subscription: "/getCurrentUser",
+ subscription: '/getCurrentUser',
secureHandler: ({ res, user: { _id, email, cacheDocumentIds } }) => res.send(JSON.stringify({ id: _id, email, cacheDocumentIds })),
- publicHandler: ({ res }) => res.send(JSON.stringify({ id: "__guest__", email: "" }))
+ publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })),
});
register({
@@ -80,7 +81,7 @@ export default class UserManager extends ApiManager {
const validated = await new Promise<Opt<boolean>>(resolve => {
bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
if (err || !passwords_match) {
- result.error = [{ msg: "Incorrect current password" }];
+ result.error = [{ msg: 'Incorrect current password' }];
res.send(result);
resolve(undefined);
} else {
@@ -93,10 +94,10 @@ export default class UserManager extends ApiManager {
return;
}
- req.assert("new_pass", "Password must be at least 4 characters long").len({ min: 4 });
- req.assert("new_confirm", "Passwords do not match").equals(new_pass);
+ req.assert('new_pass', 'Password must be at least 4 characters long').len({ min: 4 });
+ req.assert('new_confirm', 'Passwords do not match').equals(new_pass);
if (curr_pass === new_pass) {
- result.error = [{ msg: "Current and new password are the same" }];
+ result.error = [{ msg: 'Current and new password are the same' }];
}
// was there error in validating new passwords?
if (req.validationErrors()) {
@@ -113,17 +114,17 @@ export default class UserManager extends ApiManager {
user.save(err => {
if (err) {
- result.error = [{ msg: "Error while saving new password" }];
+ result.error = [{ msg: 'Error while saving new password' }];
}
});
res.send(result);
- }
+ },
});
register({
method: Method.GET,
- subscription: "/activity",
+ subscription: '/activity',
secureHandler: ({ res }) => {
const now = Date.now();
@@ -135,25 +136,23 @@ export default class UserManager extends ApiManager {
const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user);
if (socketPair && !socketPair[0].disconnected) {
const duration = now - time;
- const target = (duration / 1000) < (60 * 5) ? activeTimes : inactiveTimes;
+ const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
target.push({ user, duration });
}
}
- const process = (target: { user: string, duration: number }[]) => {
+ const process = (target: { user: string; duration: number }[]) => {
const comparator = (first: ActivityUnit, second: ActivityUnit) => first.duration - second.duration;
const sorted = target.sort(comparator);
return sorted.map(({ user, duration }) => `${user} (${msToTime(duration)})`);
};
- res.render("user_activity.pug", {
- title: "User Activity",
+ res.render('user_activity.pug', {
+ title: 'User Activity',
active: process(activeTimes),
- inactive: process(inactiveTimes)
+ inactive: process(inactiveTimes),
});
- }
+ },
});
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/applied_session_agent.ts b/src/server/DashSession/Session/agents/applied_session_agent.ts
index 12064668b..8339a06dc 100644
--- a/src/server/DashSession/Session/agents/applied_session_agent.ts
+++ b/src/server/DashSession/Session/agents/applied_session_agent.ts
@@ -9,8 +9,8 @@ export abstract class AppliedSessionAgent {
// the following two methods allow the developer to create a custom
// session and use the built in customization options for each thread
- protected abstract async initializeMonitor(monitor: Monitor): Promise<string>;
- protected abstract async initializeServerWorker(): Promise<ServerWorker>;
+ protected abstract initializeMonitor(monitor: Monitor): Promise<string>;
+ protected abstract initializeServerWorker(): Promise<ServerWorker>;
private launched = false;
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 552ab57a5..cae35da60 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -1,9 +1,9 @@
import { green, red } from 'colors';
import { ExifImage } from 'exif';
+import * as exifr from 'exifr';
import { File } from 'formidable';
import { createWriteStream, existsSync, readFileSync, rename, unlinkSync, writeFile } from 'fs';
import * as path from 'path';
-import * as exifr from 'exifr';
import { basename } from "path";
import * as sharp from 'sharp';
import { Stream } from 'stream';
@@ -17,9 +17,12 @@ import { resolvedServerUrl } from "./server_Initialization";
import { AcceptableMedia, Upload } from './SharedMediaTypes';
import request = require('request-promise');
import formidable = require('formidable');
+import { file } from 'jszip';
+import { csvParser } from './DataVizUtils';
const { exec } = require("child_process");
const parse = require('pdf-parse');
const ffmpeg = require("fluent-ffmpeg");
+const fs = require("fs");
const requestImageSize = require("../client/util/request-image-size");
export enum SizeSuffix {
@@ -60,6 +63,43 @@ export namespace DashUploadUtils {
const type = "content-type";
const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; //TODO:glr
+
+ export async function concatVideos(filePaths: string[]): Promise<Upload.AccessPathInfo> {
+ // make a list of paths to create the ordered text file for ffmpeg
+ const inputListName = 'concat.txt';
+ const textFilePath = path.join(filesDirectory, inputListName);
+ // make a list of paths to create the ordered text file for ffmpeg
+ const filePathsText = filePaths.map(filePath => `file '${filePath}'`).join('\n');
+ // write the text file to the file system
+ writeFile(textFilePath, filePathsText, (err) => console.log(err));
+
+ // make output file name based on timestamp
+ const outputFileName = `output-${Utils.GenerateGuid()}.mp4`;
+ // create the output file path in the videos directory
+ const outputFilePath = path.join(pathToDirectory(Directory.videos), outputFileName);
+
+ // concatenate the videos
+ await new Promise((resolve, reject) => {
+ var merge = ffmpeg();
+ merge.input(textFilePath)
+ .inputOptions(['-f concat', '-safe 0'])
+ .outputOptions('-c copy')
+ //.videoCodec("copy")
+ .save(outputFilePath)
+ .on("error", reject)
+ .on("end", resolve);
+ })
+
+ // delete concat.txt from the file system
+ unlinkSync(textFilePath);
+ // delete the old segment videos from the server
+ filePaths.forEach(filePath => unlinkSync(filePath));
+
+ // return the path(s) to the output file
+ return {
+ accessPaths: getAccessPaths(Directory.videos, outputFileName)
+ }
+ }
export function uploadYoutube(videoId: string): Promise<Upload.FileResponse> {
console.log("UPLOAD " + videoId);
@@ -85,7 +125,7 @@ export namespace DashUploadUtils {
const category = types[0];
let format = `.${types[1]}`;
console.log(green(`Processing upload of file (${name}) and format (${format}) with upload type (${type}) in category (${category}).`));
-
+
switch (category) {
case "image":
if (imageFormats.includes(format)) {
@@ -94,6 +134,7 @@ export namespace DashUploadUtils {
}
case "video":
if (format.includes("x-matroska")) {
+ console.log("case video");
await new Promise(res => ffmpeg(file.path)
.videoCodec("copy") // this will copy the data instead of reencode it
.save(file.path.replace(".mkv", ".mp4"))
@@ -116,6 +157,11 @@ export namespace DashUploadUtils {
if (audioFormats.includes(format)) {
return UploadAudio(file, format);
}
+ case "text":
+ if (types[1] == "csv") {
+ return UploadCsv(file);
+ }
+
}
console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`));
@@ -135,6 +181,16 @@ export namespace DashUploadUtils {
return MoveParsedFile(file, Directory.pdfs, undefined, result.text);
}
+ async function UploadCsv(file: File) {
+ const { path: sourcePath } = file;
+ // read the file as a string
+ const data = readFileSync(sourcePath, 'utf8');
+ // split the string into an array of lines
+ return MoveParsedFile(file, Directory.csv, undefined, data);
+ // console.log(csvParser(data));
+
+ }
+
const manualSuffixes = [".webm"];
async function UploadAudio(file: File, format: string) {
@@ -220,6 +276,7 @@ export namespace DashUploadUtils {
}
let resolvedUrl: string;
/**
+ *
* At this point, we want to take whatever url we have and make sure it's requestable.
* Anything that's hosted by some other website already is, but if the url is a local file url
* (locates the file on this server machine), we have to resolve the client side url by cutting out the
diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts
new file mode 100644
index 000000000..4fd0ca6ff
--- /dev/null
+++ b/src/server/DataVizUtils.ts
@@ -0,0 +1,13 @@
+export function csvParser(csv: string) {
+ const lines = csv.split("\n");
+ const headers = lines[0].split(",");
+ const data = lines.slice(1).map(line => {
+ const values = line.split(",");
+ const obj: any = {};
+ for (let i = 0; i < headers.length; i++) {
+ obj[headers[i]] = values[i];
+ }
+ return obj;
+ });
+ return data;
+} \ No newline at end of file
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index aa9bfcfa7..5683cd539 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,12 +1,12 @@
import { cyan, green, red } from 'colors';
import { Express, Request, Response } from 'express';
-import { AdminPriviliges } from ".";
-import { DashUserModel } from "./authentication/DashUserModel";
-import RouteSubscriber from "./RouteSubscriber";
+import { AdminPriviliges } from '.';
+import { DashUserModel } from './authentication/DashUserModel';
+import RouteSubscriber from './RouteSubscriber';
export enum Method {
GET,
- POST
+ POST,
}
export interface CoreArguments {
@@ -33,13 +33,13 @@ const registered = new Map<string, Set<Method>>();
enum RegistrationError {
Malformed,
- Duplicate
+ Duplicate,
}
export default class RouteManager {
private server: Express;
private _isRelease: boolean;
- private failedRegistrations: { route: string, reason: RegistrationError }[] = [];
+ private failedRegistrations: { route: string; reason: RegistrationError }[] = [];
public get isRelease() {
return this._isRelease;
@@ -74,39 +74,42 @@ export default class RouteManager {
}
process.exit(1);
} else {
- console.log(green("all server routes have been successfully registered:"));
- Array.from(registered.keys()).sort().forEach(route => console.log(cyan(route)));
+ console.log(green('all server routes have been successfully registered:'));
+ Array.from(registered.keys())
+ .sort()
+ .forEach(route => console.log(cyan(route)));
console.log();
}
- }
+ };
static routes: string[] = [];
/**
- *
- * @param initializer
+ *
+ * @param initializer
*/
addSupervisedRoute = (initializer: RouteInitializer): void => {
const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer;
- typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription);
+ typeof initializer.subscription === 'string' && RouteManager.routes.push(initializer.subscription);
initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root);
- initializer.subscription instanceof Array && initializer.subscription.map(sub => {
- typeof (sub) === "string" && RouteManager.routes.push(sub);
- sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
- });
+ initializer.subscription instanceof Array &&
+ initializer.subscription.map(sub => {
+ typeof sub === 'string' && RouteManager.routes.push(sub);
+ sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
+ });
const isRelease = this._isRelease;
const supervised = async (req: Request, res: Response) => {
let user = req.user as Partial<DashUserModel> | undefined;
const { originalUrl: target } = req;
- if (process.env.DB === "MEM" && !user) {
- user = { id: "guest", email: "", userDocumentId: "guestDocId" };
+ if (process.env.DB === 'MEM' && !user) {
+ user = { id: 'guest', email: 'guest', userDocumentId: '__guest__' };
}
const core = { req, res, isRelease };
const tryExecute = async (toExecute: (args: any) => any | Promise<any>, args: any) => {
try {
await toExecute(args);
} catch (e) {
- console.log(red(target), user && ("email" in user) ? "<user logged out>" : undefined);
+ console.log(red(target), user && 'email' in user ? '<user logged out>' : undefined);
if (errorHandler) {
errorHandler({ ...core, error: e });
} else {
@@ -119,7 +122,7 @@ export default class RouteManager {
if (AdminPriviliges.get(user.id)) {
AdminPriviliges.delete(user.id);
} else {
- return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`);
+ return res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
}
}
await tryExecute(secureHandler, { ...core, user });
@@ -128,10 +131,10 @@ export default class RouteManager {
if (publicHandler) {
await tryExecute(publicHandler, core);
if (!res.headersSent) {
- res.redirect("/login");
+ // res.redirect("/login");
}
} else {
- res.redirect("/login");
+ res.redirect('/login');
}
}
setTimeout(() => {
@@ -144,7 +147,7 @@ export default class RouteManager {
};
const subscribe = (subscriber: RouteSubscriber | string) => {
let route: string;
- if (typeof subscriber === "string") {
+ if (typeof subscriber === 'string') {
route = subscriber;
} else {
route = subscriber.build;
@@ -152,7 +155,7 @@ export default class RouteManager {
if (!/^\/$|^\/[A-Za-z\*]+(\/\:[A-Za-z?_\*]+)*$/g.test(route)) {
this.failedRegistrations.push({
reason: RegistrationError.Malformed,
- route
+ route,
});
} else {
const existing = registered.get(route);
@@ -160,7 +163,7 @@ export default class RouteManager {
if (existing.has(method)) {
this.failedRegistrations.push({
reason: RegistrationError.Duplicate,
- route
+ route,
});
return;
}
@@ -184,15 +187,14 @@ export default class RouteManager {
} else {
subscribe(subscription);
}
- }
-
+ };
}
export const STATUS = {
OK: 200,
BAD_REQUEST: 400,
EXECUTION_ERROR: 500,
- PERMISSION_DENIED: 403
+ PERMISSION_DENIED: 403,
};
export function _error(res: Response, message: string, error?: any) {
diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts
index b736f0d35..52d876e95 100644
--- a/src/server/authentication/AuthenticationManager.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -1,14 +1,14 @@
-import { default as User, DashUserModel } from "./DashUserModel";
-import { Request, Response, NextFunction } from "express";
-import * as passport from "passport";
-import { IVerifyOptions } from "passport-local";
-import "./Passport";
-import flash = require("express-flash");
+import { default as User, DashUserModel, initializeGuest } from './DashUserModel';
+import { Request, Response, NextFunction } from 'express';
+import * as passport from 'passport';
+import { IVerifyOptions } from 'passport-local';
+import './Passport';
+import flash = require('express-flash');
import * as async from 'async';
import * as nodemailer from 'nodemailer';
-import c = require("crypto");
-import { Utils } from "../../Utils";
-import { MailOptions } from "nodemailer/lib/stream-transport";
+import c = require('crypto');
+import { emptyFunction, Utils } from '../../Utils';
+import { MailOptions } from 'nodemailer/lib/stream-transport';
/**
* GET /signup
@@ -17,10 +17,10 @@ import { MailOptions } from "nodemailer/lib/stream-transport";
*/
export let getSignup = (req: Request, res: Response) => {
if (req.user) {
- return res.redirect("/home");
+ return res.redirect('/home');
}
- res.render("signup.pug", {
- title: "Sign Up",
+ res.render('signup.pug', {
+ title: 'Sign Up',
user: req.user,
});
};
@@ -30,45 +30,50 @@ export let getSignup = (req: Request, res: Response) => {
* Create a new local account.
*/
export let postSignup = (req: Request, res: Response, next: NextFunction) => {
- req.assert("email", "Email is not valid").isEmail();
- req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
- req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
- req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
+ const email = req.body.email as String;
+ req.assert('email', 'Email is not valid').isEmail();
+ req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 });
+ req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
+ req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
const errors = req.validationErrors();
if (errors) {
- return res.redirect("/signup");
+ return res.redirect('/signup');
}
- const email = req.body.email as String;
const password = req.body.password;
const model = {
email,
password,
- userDocumentId: Utils.GenerateGuid(),
- sharingDocumentId: Utils.GenerateGuid(),
- linkDatabaseId: Utils.GenerateGuid(),
- cacheDocumentIds: ""
+ userDocumentId: email === 'guest' ? '__guest__' : Utils.GenerateGuid(),
+ sharingDocumentId: email === 'guest' ? 2 : Utils.GenerateGuid(),
+ linkDatabaseId: email === 'guest' ? 3 : Utils.GenerateGuid(),
+ cacheDocumentIds: '',
} as Partial<DashUserModel>;
const user = new User(model);
User.findOne({ email }, (err: any, existingUser: any) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
if (existingUser) {
- return res.redirect("/login");
+ return res.redirect('/login');
}
user.save((err: any) => {
- if (err) { return next(err); }
- req.logIn(user, (err) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
+ req.logIn(user, err => {
+ if (err) {
+ return next(err);
+ }
tryRedirectToTarget(req, res);
});
});
});
-
};
const tryRedirectToTarget = (req: Request, res: Response) => {
@@ -76,11 +81,10 @@ const tryRedirectToTarget = (req: Request, res: Response) => {
if (req.session && target) {
res.redirect(target);
} else {
- res.redirect("/home");
+ res.redirect('/home');
}
};
-
/**
* GET /login
* Login page.
@@ -88,11 +92,11 @@ const tryRedirectToTarget = (req: Request, res: Response) => {
export let getLogin = (req: Request, res: Response) => {
if (req.user) {
//req.session.target = undefined;
- return res.redirect("/home");
+ return res.redirect('/home');
}
- res.render("login.pug", {
- title: "Log In",
- user: req.user
+ res.render('login.pug', {
+ title: 'Log In',
+ user: req.user,
});
};
@@ -102,27 +106,38 @@ export let getLogin = (req: Request, res: Response) => {
* On failure, redirect to signup page
*/
export let postLogin = (req: Request, res: Response, next: NextFunction) => {
- req.assert("email", "Email is not valid").isEmail();
- req.assert("password", "Password cannot be blank").notEmpty();
- req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
-
- const errors = req.validationErrors();
+ if (req.body.email === '') {
+ User.findOne({ email: 'guest' }, (err: any, user: DashUserModel) => !user && initializeGuest());
+ req.body.email = 'guest';
+ req.body.password = 'guest';
+ } else {
+ req.assert('email', 'Email is not valid').isEmail();
+ req.assert('password', 'Password cannot be blank').notEmpty();
+ req.sanitize('email').normalizeEmail({ gmail_remove_dots: false });
+ }
- if (errors) {
- req.flash("errors", "Unable to login at this time. Please try again.");
- return res.redirect("/signup");
+ if (req.validationErrors()) {
+ req.flash('errors', 'Unable to login at this time. Please try again.');
+ return res.redirect('/signup');
}
- passport.authenticate("local", (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
- if (err) { next(err); return; }
+ const callback = (err: Error, user: DashUserModel, _info: IVerifyOptions) => {
+ if (err) {
+ next(err);
+ return;
+ }
if (!user) {
- return res.redirect("/signup");
+ return res.redirect('/signup');
}
- req.logIn(user, (err) => {
- if (err) { next(err); return; }
+ req.logIn(user, err => {
+ if (err) {
+ next(err);
+ return;
+ }
tryRedirectToTarget(req, res);
});
- })(req, res, next);
+ };
+ setTimeout(() => passport.authenticate('local', callback)(req, res, next), 500);
};
/**
@@ -131,140 +146,154 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
* and destroys the user's current session.
*/
export let getLogout = (req: Request, res: Response) => {
- req.logout();
+ req.logout(emptyFunction);
const sess = req.session;
if (sess) {
- sess.destroy((err) => { if (err) { console.log(err); } });
+ sess.destroy(err => {
+ if (err) {
+ console.log(err);
+ }
+ });
}
- res.redirect("/login");
+ res.redirect('/login');
};
export let getForgot = function (req: Request, res: Response) {
- res.render("forgot.pug", {
- title: "Recover Password",
+ res.render('forgot.pug', {
+ title: 'Recover Password',
user: req.user,
});
};
export let postForgot = function (req: Request, res: Response, next: NextFunction) {
const email = req.body.email;
- async.waterfall([
- function (done: any) {
- c.randomBytes(20, function (err: any, buffer: Buffer) {
- if (err) {
- done(null);
- return;
- }
- done(null, buffer.toString('hex'));
- });
- },
- function (token: string, done: any) {
- User.findOne({ email }, function (err: any, user: DashUserModel) {
- if (!user) {
- // NO ACCOUNT WITH SUBMITTED EMAIL
- res.redirect("/forgotPassword");
- return;
- }
- user.passwordResetToken = token;
- user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
- user.save(function (err: any) {
- done(null, token, user);
+ async.waterfall(
+ [
+ function (done: any) {
+ c.randomBytes(20, function (err: any, buffer: Buffer) {
+ if (err) {
+ done(null);
+ return;
+ }
+ done(null, buffer.toString('hex'));
});
- });
- },
- function (token: Uint16Array, user: DashUserModel, done: any) {
- const smtpTransport = nodemailer.createTransport({
- service: 'Gmail',
- auth: {
- user: 'browndashptc@gmail.com',
- pass: 'TsarNicholas#2'
- }
- });
- const mailOptions = {
- to: user.email,
- from: 'browndashptc@gmail.com',
- subject: 'Dash Password Reset',
- text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
- 'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
- 'http://' + req.headers.host + '/resetPassword/' + token + '\n\n' +
- 'If you did not request this, please ignore this email and your password will remain unchanged.\n'
- } as MailOptions;
- smtpTransport.sendMail(mailOptions, function (err: Error | null) {
- // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.');
- done(null, err, 'done');
- });
+ },
+ function (token: string, done: any) {
+ User.findOne({ email }, function (err: any, user: DashUserModel) {
+ if (!user) {
+ // NO ACCOUNT WITH SUBMITTED EMAIL
+ res.redirect('/forgotPassword');
+ return;
+ }
+ user.passwordResetToken = token;
+ user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
+ user.save(function (err: any) {
+ done(null, token, user);
+ });
+ });
+ },
+ function (token: Uint16Array, user: DashUserModel, done: any) {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'browndashptc@gmail.com',
+ pass: 'TsarNicholas#2',
+ },
+ });
+ const mailOptions = {
+ to: user.email,
+ from: 'browndashptc@gmail.com',
+ subject: 'Dash Password Reset',
+ text:
+ 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
+ 'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
+ 'http://' +
+ req.headers.host +
+ '/resetPassword/' +
+ token +
+ '\n\n' +
+ 'If you did not request this, please ignore this email and your password will remain unchanged.\n',
+ } as MailOptions;
+ smtpTransport.sendMail(mailOptions, function (err: Error | null) {
+ // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.');
+ done(null, err, 'done');
+ });
+ },
+ ],
+ function (err) {
+ if (err) return next(err);
+ res.redirect('/forgotPassword');
}
- ], function (err) {
- if (err) return next(err);
- res.redirect("/forgotPassword");
- });
+ );
};
export let getReset = function (req: Request, res: Response) {
User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) {
if (!user || err) {
- return res.redirect("/forgotPassword");
+ return res.redirect('/forgotPassword');
}
- res.render("reset.pug", {
- title: "Reset Password",
+ res.render('reset.pug', {
+ title: 'Reset Password',
user: req.user,
});
});
};
export let postReset = function (req: Request, res: Response) {
- async.waterfall([
- function (done: any) {
- User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) {
- if (!user || err) {
- return res.redirect('back');
- }
+ async.waterfall(
+ [
+ function (done: any) {
+ User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) {
+ if (!user || err) {
+ return res.redirect('back');
+ }
- req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
- req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
+ req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 });
+ req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
- if (req.validationErrors()) {
- return res.redirect('back');
- }
+ if (req.validationErrors()) {
+ return res.redirect('back');
+ }
- user.password = req.body.password;
- user.passwordResetToken = undefined;
- user.passwordResetExpires = undefined;
+ user.password = req.body.password;
+ user.passwordResetToken = undefined;
+ user.passwordResetExpires = undefined;
- user.save(function (err) {
- if (err) {
- res.redirect("/login");
- return;
- }
- req.logIn(user, function (err) {
+ user.save(function (err) {
if (err) {
+ res.redirect('/login');
return;
}
+ req.logIn(user, function (err) {
+ if (err) {
+ return;
+ }
+ });
+ done(null, user);
});
- done(null, user);
});
- });
- },
- function (user: DashUserModel, done: any) {
- const smtpTransport = nodemailer.createTransport({
- service: 'Gmail',
- auth: {
- user: 'browndashptc@gmail.com',
- pass: 'TsarNicholas#2'
- }
- });
- const mailOptions = {
- to: user.email,
- from: 'browndashptc@gmail.com',
- subject: 'Your password has been changed',
- text: 'Hello,\n\n' +
- 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
- } as MailOptions;
- smtpTransport.sendMail(mailOptions, function (err) {
- done(null, err);
- });
+ },
+ function (user: DashUserModel, done: any) {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'browndashptc@gmail.com',
+ pass: 'TsarNicholas#2',
+ },
+ });
+ const mailOptions = {
+ to: user.email,
+ from: 'browndashptc@gmail.com',
+ subject: 'Your password has been changed',
+ text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n',
+ } as MailOptions;
+ smtpTransport.sendMail(mailOptions, function (err) {
+ done(null, err);
+ });
+ },
+ ],
+ function (err) {
+ res.redirect('/login');
}
- ], function (err) {
- res.redirect("/login");
- });
-}; \ No newline at end of file
+ );
+};
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index bee28b96d..a1883beab 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -1,13 +1,13 @@
//@ts-ignore
-import * as bcrypt from "bcrypt-nodejs";
+import * as bcrypt from 'bcrypt-nodejs';
//@ts-ignore
import * as mongoose from 'mongoose';
export type DashUserModel = mongoose.Document & {
- email: String,
- password: string,
- passwordResetToken?: string,
- passwordResetExpires?: Date,
+ email: String;
+ password: string;
+ passwordResetToken?: string;
+ passwordResetExpires?: Date;
userDocumentId: string;
sharingDocumentId: string;
@@ -15,66 +15,74 @@ export type DashUserModel = mongoose.Document & {
cacheDocumentIds: string;
profile: {
- name: string,
- gender: string,
- location: string,
- website: string,
- picture: string
- },
+ name: string;
+ gender: string;
+ location: string;
+ website: string;
+ picture: string;
+ };
- comparePassword: comparePasswordFunction,
+ comparePassword: comparePasswordFunction;
};
type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void;
export type AuthToken = {
- accessToken: string,
- kind: string
+ accessToken: string;
+ kind: string;
};
-const userSchema = new mongoose.Schema({
- email: String,
- password: String,
- passwordResetToken: String,
- passwordResetExpires: Date,
+const userSchema = new mongoose.Schema(
+ {
+ email: String,
+ password: String,
+ passwordResetToken: String,
+ passwordResetExpires: Date,
- userDocumentId: String, // id that identifies a document which hosts all of a user's account data
- sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
- linkDatabaseId: String,
- cacheDocumentIds: String, // set of document ids to retreive on startup
+ userDocumentId: String, // id that identifies a document which hosts all of a user's account data
+ sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users
+ linkDatabaseId: String,
+ cacheDocumentIds: String, // set of document ids to retreive on startup
- facebook: String,
- twitter: String,
- google: String,
+ facebook: String,
+ twitter: String,
+ google: String,
- profile: {
- name: String,
- gender: String,
- location: String,
- website: String,
- picture: String
- }
-}, { timestamps: true });
+ profile: {
+ name: String,
+ gender: String,
+ location: String,
+ website: String,
+ picture: String,
+ },
+ },
+ { timestamps: true }
+);
/**
* Password hash middleware.
*/
-userSchema.pre("save", function save(next) {
+userSchema.pre('save', function save(next) {
const user = this as DashUserModel;
- if (!user.isModified("password")) {
+ if (!user.isModified('password')) {
return next();
}
bcrypt.genSalt(10, (err: any, salt: string) => {
if (err) {
return next(err);
}
- bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => {
- if (err) {
- return next(err);
+ bcrypt.hash(
+ user.password,
+ salt,
+ () => void {},
+ (err: mongoose.Error, hash: string) => {
+ if (err) {
+ return next(err);
+ }
+ user.password = hash;
+ next();
}
- user.password = hash;
- next();
- });
+ );
});
});
@@ -88,5 +96,15 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel,
userSchema.methods.comparePassword = comparePassword;
-const User = mongoose.model("User", userSchema);
-export default User; \ No newline at end of file
+const User = mongoose.model('User', userSchema);
+export function initializeGuest() {
+ new User({
+ email: 'guest',
+ password: 'guest',
+ userDocumentId: '__guest__',
+ sharingDocumentId: '2',
+ linkDatabaseId: '3',
+ cacheDocumentIds: '',
+ }).save();
+}
+export default User;
diff --git a/src/server/database.ts b/src/server/database.ts
index 2a55c14de..725b66836 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -34,7 +34,13 @@ export namespace Database {
console.log(`mongoose established default connection at ${url}`);
resolve();
});
- mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, dbName: schema });
+ mongoose.connect(url, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ dbName: schema,
+ // reconnectTries: Number.MAX_VALUE,
+ // reconnectInterval: 1000,
+ });
});
}
} catch (e) {
diff --git a/src/server/index.ts b/src/server/index.ts
index f8c32103b..6e6bde3cb 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,35 +1,35 @@
require('dotenv').config();
-import { yellow } from "colors";
+import { yellow } from 'colors';
import * as mobileDetect from 'mobile-detect';
import * as path from 'path';
import * as qs from 'query-string';
-import { log_execution } from "./ActionUtilities";
-import DeleteManager from "./ApiManagers/DeleteManager";
+import { log_execution } from './ActionUtilities';
+import DeleteManager from './ApiManagers/DeleteManager';
import DownloadManager from './ApiManagers/DownloadManager';
-import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
-import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import PDFManager from "./ApiManagers/PDFManager";
+import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
+import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
+import PDFManager from './ApiManagers/PDFManager';
import { SearchManager } from './ApiManagers/SearchManager';
-import SessionManager from "./ApiManagers/SessionManager";
-import UploadManager from "./ApiManagers/UploadManager";
+import SessionManager from './ApiManagers/SessionManager';
+import UploadManager from './ApiManagers/UploadManager';
import UserManager from './ApiManagers/UserManager';
import UtilManager from './ApiManagers/UtilManager';
import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
-import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
-import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
+import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
+import { DashSessionAgent } from './DashSession/DashSessionAgent';
+import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent';
import { DashUploadUtils } from './DashUploadUtils';
import { Database } from './database';
-import { Logger } from "./ProcessFactory";
+import { Logger } from './ProcessFactory';
import RouteManager, { Method, PublicHandler } from './RouteManager';
import RouteSubscriber from './RouteSubscriber';
import initializeServer, { resolvedPorts } from './server_Initialization';
export const AdminPriviliges: Map<string, boolean> = new Map();
-export const onWindows = process.platform === "win32";
+export const onWindows = process.platform === 'win32';
export let sessionAgent: AppliedSessionAgent;
-export const publicDirectory = path.resolve(__dirname, "public");
-export const filesDirectory = path.resolve(publicDirectory, "files");
+export const publicDirectory = path.resolve(__dirname, 'public');
+export const filesDirectory = path.resolve(publicDirectory, 'files');
/**
* These are the functions run before the server starts
@@ -43,11 +43,11 @@ async function preliminaryFunctions() {
await GoogleCredentialsLoader.loadCredentials();
SSL.loadCredentials();
GoogleApiServerUtils.processProjectCredentials();
- if (process.env.DB !== "MEM") {
+ if (process.env.DB !== 'MEM') {
await log_execution({
- startMessage: "attempting to initialize mongodb connection",
- endMessage: "connection outcome determined",
- action: Database.tryInitializeConnection
+ startMessage: 'attempting to initialize mongodb connection',
+ endMessage: 'connection outcome determined',
+ action: Database.tryInitializeConnection,
});
}
}
@@ -56,27 +56,16 @@ async function preliminaryFunctions() {
* Either clustered together as an API manager
* or individually referenced below, by the completion
* of this function's execution, all routes will
- * be registered on the server
+ * be registered on the server
* @param router the instance of the route manager
* that will manage the registration of new routes
* with the server
*/
function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
- const managers = [
- new SessionManager(),
- new UserManager(),
- new UploadManager(),
- new DownloadManager(),
- new SearchManager(),
- new PDFManager(),
- new DeleteManager(),
- new UtilManager(),
- new GeneralGoogleManager(),
- new GooglePhotosManager(),
- ];
+ const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new PDFManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), new GooglePhotosManager()];
// initialize API Managers
- console.log(yellow("\nregistering server routes..."));
+ console.log(yellow('\nregistering server routes...'));
managers.forEach(manager => manager.register(addSupervisedRoute));
/**
@@ -84,88 +73,87 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
*/
addSupervisedRoute({
method: Method.GET,
- subscription: "/",
- secureHandler: ({ res }) => res.redirect("/home")
+ subscription: '/',
+ secureHandler: ({ res }) => res.redirect('/home'),
});
-
addSupervisedRoute({
method: Method.GET,
- subscription: "/serverHeartbeat",
- secureHandler: ({ res }) => res.send(true)
+ subscription: '/serverHeartbeat',
+ secureHandler: ({ res }) => res.send(true),
});
addSupervisedRoute({
method: Method.GET,
- subscription: "/resolvedPorts",
- secureHandler: ({ res }) => res.send(resolvedPorts)
+ subscription: '/resolvedPorts',
+ secureHandler: ({ res }) => res.send(resolvedPorts),
+ publicHandler: ({ res }) => res.send(resolvedPorts),
});
const serve: PublicHandler = ({ req, res }) => {
- const detector = new mobileDetect(req.headers['user-agent'] || "");
+ const detector = new mobileDetect(req.headers['user-agent'] || '');
const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
res.sendFile(path.join(__dirname, '../../deploy/' + filename));
};
/**
- * Serves a simple password input box for any
+ * Serves a simple password input box for any
*/
addSupervisedRoute({
method: Method.GET,
- subscription: new RouteSubscriber("admin").add("previous_target"),
+ subscription: new RouteSubscriber('admin').add('previous_target'),
secureHandler: ({ res, isRelease }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect("/home");
+ return res.redirect('/home');
}
- res.render("admin.pug", { title: "Enter Administrator Password" });
- }
+ res.render('admin.pug', { title: 'Enter Administrator Password' });
+ },
});
addSupervisedRoute({
method: Method.POST,
- subscription: new RouteSubscriber("admin").add("previous_target"),
+ subscription: new RouteSubscriber('admin').add('previous_target'),
secureHandler: async ({ req, res, isRelease, user: { id } }) => {
const { PASSWORD } = process.env;
if (!(isRelease && PASSWORD)) {
- return res.redirect("/home");
+ return res.redirect('/home');
}
const { password } = req.body;
const { previous_target } = req.params;
let redirect: string;
if (password === PASSWORD) {
AdminPriviliges.set(id, true);
- redirect = `/${previous_target.replace(":", "/")}`;
+ redirect = `/${previous_target.replace(':', '/')}`;
} else {
redirect = `/admin/${previous_target}`;
}
res.redirect(redirect);
- }
+ },
});
addSupervisedRoute({
method: Method.GET,
- subscription: ["/home", new RouteSubscriber("doc").add("docId")],
+ subscription: ['/home', new RouteSubscriber('doc').add('docId')],
secureHandler: serve,
publicHandler: ({ req, res, ...remaining }) => {
const { originalUrl: target } = req;
- const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === "true";
- const docAccess = target.startsWith("/doc/");
+ const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === 'true';
+ const docAccess = target.startsWith('/doc/');
// since this is the public handler, there's no meaning of '/home' to speak of
// since there's no user logged in, so the only viable operation
// for a guest is to look at a shared document
- if (sharing && docAccess) {
+ if (docAccess) {
serve({ req, res, ...remaining });
} else {
- res.redirect("/login");
+ res.redirect('/login');
}
- }
+ },
});
logRegistrationOutcome();
}
-
/**
* This function can be used in two different ways. If not in release mode,
* this is simply the logic that is invoked to start the server. In release mode,
@@ -174,9 +162,9 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
*/
export async function launchServer() {
await log_execution({
- startMessage: "\nstarting execution of preliminary functions",
- endMessage: "completed preliminary functions\n",
- action: preliminaryFunctions
+ startMessage: '\nstarting execution of preliminary functions',
+ endMessage: 'completed preliminary functions\n',
+ action: preliminaryFunctions,
});
await initializeServer(routeSetter);
}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 24cc3b796..fd000a83c 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -171,54 +171,53 @@ function registerCorsProxy(server: express.Express) {
function proxyServe(req: any, requrl: string, response: any) {
const htmlBodyMemoryStream = new (require('memorystream'))();
- req.headers.cookie = "";
- req.pipe(request(requrl))
- .on("error", (e: any) => console.log(`Malformed CORS url: ${requrl}`, e))
- .on("end", () => {
- var rewrittenHtmlBody: any = undefined;
- req.pipe(request(requrl))
- .on("response", (res: any) => {
- const headers = Object.keys(res.headers);
- const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
- headers.forEach(headerName => {
- const header = res.headers[headerName];
- if (Array.isArray(header)) {
- res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
- } else if (headerCharRegex.test(header || "")) {
- delete res.headers[headerName];
- }
- if (headerName === "content-encoding" && header.includes("gzip")) {
- try {
- const replacer = (match: any, href: string, offset: any, string: any) => {
- return `href="${resolvedServerUrl + "/corsProxy/http" + href}"`;
- };
- const zipToStringDecoder = new (require('string_decoder').StringDecoder)('utf8');
- // const htmlText = zipToStringDecoder.write(zlib.gunzipSync(htmlBodyMemoryStream.read()).toString('utf8')
- // .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- // .replace(/href="http([^"]*)"/g, replacer)
- // .replace(/target="_blank"/g, ""));
- // rewrittenHtmlBody = zlib.gzipSync(htmlText);
- const bodyStream = htmlBodyMemoryStream.read();
- if (bodyStream) {
- const htmlText = zipToStringDecoder.write(zlib.gunzipSync(bodyStream).toString('utf8')
- .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- // .replace(/href="http([^"]*)"/g, replacer)
- .replace(/target="_blank"/g, ""));
- rewrittenHtmlBody = zlib.gzipSync(htmlText);
- } else {
- console.log("EMPTY body: href");
- }
- } catch (e) { console.log(e); }
- }
- });
- })
- .on('data', (e: any) => {
- rewrittenHtmlBody && response.send(rewrittenHtmlBody);
- rewrittenHtmlBody = undefined;
- })
- .pipe(response);
- })
- .pipe(htmlBodyMemoryStream);
+ var retrieveHTTPBody: any;
+ const sendModifiedBody = () => {
+ const header = response.headers["content-encoding"];
+ if (header && header.includes("gzip")) {
+ try {
+ const replacer = (match: any, href: string, offset: any, string: any) => {
+ return `href="${resolvedServerUrl + "/corsProxy/http" + href}"`;
+ };
+ const zipToStringDecoder = new (require('string_decoder').StringDecoder)('utf8');
+ const bodyStream = htmlBodyMemoryStream.read();
+ if (bodyStream) {
+ const htmlText = zipToStringDecoder.write(zlib.gunzipSync(bodyStream).toString('utf8')
+ .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
+ .replace(/href="https?([^"]*)"/g, replacer)
+ .replace(/target="_blank"/g, ""));
+ response.send(zlib.gzipSync(htmlText));
+ } else {
+ req.pipe(request(requrl)).pipe(response);
+ console.log("EMPTY body:" + req.url);
+ }
+ } catch (e) {
+ console.log("EROR?: ", e);
+ }
+ } else req.pipe(request(requrl)).pipe(response);
+ };
+ retrieveHTTPBody = () => {
+ req.headers.cookie = "";
+ req.pipe(request(requrl))
+ .on("error", (e: any) => console.log(`Malformed CORS url: ${requrl}`, e))
+ .on("response", (res: any) => {
+ res.headers;
+ const headers = Object.keys(res.headers);
+ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
+ headers.forEach(headerName => {
+ const header = res.headers[headerName];
+ if (Array.isArray(header)) {
+ res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
+ } else if (headerCharRegex.test(header || "")) {
+ delete res.headers[headerName];
+ } else res.headers[headerName] = header;
+ });
+ response.headers = response._headers = res.headers;
+ })
+ .on("end", sendModifiedBody)
+ .pipe(htmlBodyMemoryStream);
+ };
+ retrieveHTTPBody();
}
function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
@@ -226,12 +225,17 @@ function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
const relativeUrl = req.originalUrl;
if (!req.user) res.redirect("/home"); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home
else if (!res.headersSent && req.headers.referer?.includes("corsProxy")) { // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here.
- const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
- const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
- const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : http:s//en.wikipedia.org/wiki/Engelbart)
- const absoluteTargetBaseUrl = actualReferUrl.match(/http[s]?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
- const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
- res.redirect(redirectedProxiedUrl);
+ try {
+ const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
+ const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
+ const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
+ const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
+ const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
+ if (relativeUrl.startsWith("//")) res.redirect("http:" + relativeUrl);
+ else res.redirect(redirectedProxiedUrl);
+ } catch (e) {
+ console.log("Error embed: ", e);
+ }
} else if (relativeUrl.startsWith("/search") && !req.headers.referer?.includes("corsProxy")) { // detect search query and use default search engine
res.redirect(req.headers.referer + "corsProxy/" + encodeURIComponent("http://www.google.com" + relativeUrl));
} else {
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 1b7f5919f..9b91a35a6 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -1,24 +1,24 @@
-import * as express from "express";
-import { blue, green } from "colors";
-import { createServer, Server } from "https";
-import { networkInterfaces } from "os";
+import { blue } from 'colors';
+import * as express from 'express';
+import { createServer, Server } from 'https';
+import { networkInterfaces } from 'os';
import * as sio from 'socket.io';
-import { Socket } from "socket.io";
-import { Utils } from "../Utils";
+import { Socket } from 'socket.io';
+import { Opt } from '../fields/Doc';
+import { Utils } from '../Utils';
import { logPort } from './ActionUtilities';
-import { timeMap } from "./ApiManagers/UserManager";
-import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader";
-import YoutubeApi from "./apis/youtube/youtubeApiSample";
-import { Client } from "./Client";
-import { Database } from "./database";
-import { DocumentsCollection } from "./IDatabase";
-import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from "./Message";
-import { Search } from "./Search";
+import { timeMap } from './ApiManagers/UserManager';
+import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader';
+import YoutubeApi from './apis/youtube/youtubeApiSample';
+import { initializeGuest } from './authentication/DashUserModel';
+import { Client } from './Client';
+import { Database } from './database';
+import { DocumentsCollection } from './IDatabase';
+import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message';
+import { Search } from './Search';
import { resolvedPorts } from './server_Initialization';
-import { Opt } from "../fields/Doc";
export namespace WebSocket {
-
export let _socket: Socket;
const clients: { [key: string]: Client } = {};
export const socketMap = new Map<SocketIO.Socket, string>();
@@ -32,14 +32,14 @@ export namespace WebSocket {
resolvedPorts.socket = Number(socketPort);
}
let socketEndpoint: Opt<Server>;
- await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve));
+ await new Promise<void>(resolve => (socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)));
io = sio(socketEndpoint!, SSL.Credentials as any);
} else {
io = sio().listen(resolvedPorts.socket);
}
- logPort("websocket", resolvedPorts.socket);
+ logPort('websocket', resolvedPorts.socket);
- io.on("connection", function (socket: Socket) {
+ io.on('connection', function (socket: Socket) {
_socket = socket;
socket.use((_packet, next) => {
const userEmail = socketMap.get(socket);
@@ -70,14 +70,14 @@ export namespace WebSocket {
socket.join(room);
console.log('Client ID ' + socket.id + ' created room ' + room);
socket.emit('created', room, socket.id);
-
} else if (numClients === 1) {
console.log('Client ID ' + socket.id + ' joined room ' + room);
socket.in(room).emit('join', room);
socket.join(room);
socket.emit('joined', room, socket.id);
socket.in(room).emit('ready');
- } else { // max two clients
+ } else {
+ // max two clients
socket.emit('full', room);
}
});
@@ -97,10 +97,10 @@ export namespace WebSocket {
console.log('received bye');
});
- Utils.Emit(socket, MessageStore.Foo, "handshooken");
+ Utils.Emit(socket, MessageStore.Foo, 'handshooken');
Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
- Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+ Utils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
if (isRelease) {
@@ -126,26 +126,26 @@ export namespace WebSocket {
*/
disconnect = () => {
- socket.broadcast.emit("connection_terminated", Date.now());
+ socket.broadcast.emit('connection_terminated', Date.now());
socket.disconnect(true);
};
});
}
function processGesturePoints(socket: Socket, content: GestureContent) {
- socket.broadcast.emit("receiveGesturePoints", content);
+ socket.broadcast.emit('receiveGesturePoints', content);
}
function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) {
- socket.broadcast.emit("receiveOverlayTrigger", content);
+ socket.broadcast.emit('receiveOverlayTrigger', content);
}
function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) {
- socket.broadcast.emit("receiveUpdateOverlayPosition", content);
+ socket.broadcast.emit('receiveUpdateOverlayPosition', content);
}
function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) {
- socket.broadcast.emit("receiveMobileDocumentUpload", content);
+ socket.broadcast.emit('receiveMobileDocumentUpload', content);
}
function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
@@ -165,27 +165,22 @@ export namespace WebSocket {
const target: string[] = [];
onlyFields && target.push(DocumentsCollection);
await Database.Instance.dropSchema(...target);
- if (process.env.DISABLE_SEARCH !== "true") {
+ if (process.env.DISABLE_SEARCH !== 'true') {
await Search.clear();
}
+ initializeGuest();
}
function barReceived(socket: SocketIO.Socket, userEmail: string) {
clients[userEmail] = new Client(userEmail.toString());
const currentdate = new Date();
- const datetime = currentdate.getDate() + "/"
- + (currentdate.getMonth() + 1) + "/"
- + currentdate.getFullYear() + " @ "
- + currentdate.getHours() + ":"
- + currentdate.getMinutes() + ":"
- + currentdate.getSeconds();
+ const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds();
console.log(blue(`user ${userEmail} has connected to the web socket at: ${datetime}`));
- socketMap.set(socket, userEmail + " at " + datetime);
+ socketMap.set(socket, userEmail + ' at ' + datetime);
}
function getField([id, callback]: [string, (result?: Transferable) => void]) {
- Database.Instance.getDocument(id, (result?: Transferable) =>
- callback(result ? result : undefined));
+ Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined));
}
function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
@@ -193,9 +188,9 @@ export namespace WebSocket {
}
function setField(socket: Socket, newValue: Transferable) {
- Database.Instance.update(newValue.id, newValue, () =>
- socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients
- if (newValue.type === Types.Text) { // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
+ Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients
+ if (newValue.type === Types.Text) {
+ // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
Search.updateDocument({ id: newValue.id, data: { set: (newValue as any).data } });
}
}
@@ -213,34 +208,36 @@ export namespace WebSocket {
Database.Instance.getDocuments(ids, callback);
}
- const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
- "number": "_n",
- "string": "_t",
- "boolean": "_b",
- "image": ["_t", "url"],
- "video": ["_t", "url"],
- "pdf": ["_t", "url"],
- "audio": ["_t", "url"],
- "web": ["_t", "url"],
- "map": ["_t", "url"],
- "script": ["_t", value => value.script.originalScript],
- "RichTextField": ["_t", value => value.Text],
- "date": ["_d", value => new Date(value.date).toISOString()],
- "proxy": ["_i", "fieldId"],
- "list": ["_l", list => {
- const results = [];
- for (const value of list.fields) {
- const term = ToSearchTerm(value);
- if (term) {
- results.push(term.value);
+ const suffixMap: { [type: string]: string | [string, string | ((json: any) => any)] } = {
+ number: '_n',
+ string: '_t',
+ boolean: '_b',
+ image: ['_t', 'url'],
+ video: ['_t', 'url'],
+ pdf: ['_t', 'url'],
+ audio: ['_t', 'url'],
+ web: ['_t', 'url'],
+ map: ['_t', 'url'],
+ script: ['_t', value => value.script.originalScript],
+ RichTextField: ['_t', value => value.Text],
+ date: ['_d', value => new Date(value.date).toISOString()],
+ proxy: ['_i', 'fieldId'],
+ list: [
+ '_l',
+ list => {
+ const results = [];
+ for (const value of list.fields) {
+ const term = ToSearchTerm(value);
+ if (term) {
+ results.push(term.value);
+ }
}
- }
- return results.length ? results : null;
- }]
+ return results.length ? results : null;
+ },
+ ],
};
- function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
-
+ function ToSearchTerm(val: any): { suffix: string; value: any } | undefined {
if (val === null || val === undefined) {
return;
}
@@ -252,69 +249,79 @@ export namespace WebSocket {
}
if (Array.isArray(suffix)) {
const accessor = suffix[1];
- if (typeof accessor === "function") {
+ if (typeof accessor === 'function') {
val = accessor(val);
} else {
val = val[accessor];
-
}
suffix = suffix[0];
-
}
return { suffix, value: val };
}
function getSuffix(value: string | [string, any]): string {
- return typeof value === "string" ? value : value[0];
+ return typeof value === 'string' ? value : value[0];
}
function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
- diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet;// convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
+ diff.diff.$set = diff.diff.$addToSet;
+ delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
const newListItems = diff.diff.$set[updatefield]?.fields;
if (!newListItems) {
- console.log("Error: addToListField - no new list items");
+ console.log('Error: addToListField - no new list items');
return;
}
- const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || [];
- diff.diff.$set[updatefield].fields = [...curList, ...newListItems];//, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
+ const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || [];
+ diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; //, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
delete diff.diff.length;
- Database.Instance.update(diff.id, diff.diff,
+ Database.Instance.update(
+ diff.id,
+ diff.diff,
() => {
if (sendBack) {
- console.log("Warning: list modified during update. Composite list is being returned.");
+ console.log('Warning: list modified during update. Composite list is being returned.');
const id = socket.id;
- socket.id = "";
+ socket.id = '';
socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
socket.id = id;
} else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
dispatchNextOp(diff.id);
- }, false);
+ },
+ false
+ );
}
function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
- diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet;
+ diff.diff.$set = diff.diff.$remFromSet;
+ delete diff.diff.$remFromSet;
const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
const remListItems = diff.diff.$set[updatefield].fields;
- const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((f: any) => f !== null) || [];
- diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem));
+ const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || [];
+ diff.diff.$set[updatefield].fields = curList?.filter(
+ (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem))
+ );
const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
delete diff.diff.length;
- Database.Instance.update(diff.id, diff.diff,
+ Database.Instance.update(
+ diff.id,
+ diff.diff,
() => {
if (sendBack) {
- console.log("SEND BACK");
+ console.log('SEND BACK');
const id = socket.id;
- socket.id = "";
+ socket.id = '';
socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
socket.id = id;
} else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
dispatchNextOp(diff.id);
- }, false);
+ },
+ false
+ );
}
- const pendingOps = new Map<string, { diff: Diff, socket: Socket }[]>();
+ const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
function dispatchNextOp(id: string) {
const next = pendingOps.get(id)!.shift();
@@ -341,7 +348,7 @@ export namespace WebSocket {
function UpdateField(socket: Socket, diff: Diff) {
if (CurUser !== socketMap.get(socket)) {
CurUser = socketMap.get(socket);
- console.log("Switch User: " + CurUser);
+ console.log('Switch User: ' + CurUser);
}
if (pendingOps.has(diff.id)) {
pendingOps.get(diff.id)!.push({ diff, socket });
@@ -357,24 +364,25 @@ export namespace WebSocket {
return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
}
function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) {
- Database.Instance.update(diff.id, diff.diff,
- () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
+ Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
const docfield = diff.diff.$set || diff.diff.$unset;
if (docfield) {
const update: any = { id: diff.id };
let dynfield = false;
for (let key in docfield) {
- if (!key.startsWith("fields.")) continue;
+ if (!key.startsWith('fields.')) continue;
dynfield = true;
const val = docfield[key];
key = key.substring(7);
- Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; });
+ Object.values(suffixMap).forEach(suf => {
+ update[key + getSuffix(suf)] = { set: null };
+ });
const term = ToSearchTerm(val);
if (term !== undefined) {
const { suffix, value } = term;
update[key + suffix] = { set: value };
if (key.endsWith('lastModified')) {
- update["lastModified" + suffix] = value;
+ update['lastModified' + suffix] = value;
}
}
}
@@ -403,6 +411,4 @@ export namespace WebSocket {
function CreateField(newValue: any) {
Database.Instance.insert(newValue);
}
-
}
-
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 068ac2159..23e4680fd 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -1,13 +1,13 @@
/// <reference types="node" />
declare module 'googlephotos';
-declare module 'react-image-lightbox-with-rotate';
declare module 'cors';
declare module 'webrtc-adapter';
declare module 'bezier-curve';
declare module 'fit-curve';
declare module 'react-audio-waveform';
+declare module 'iink-js';
declare module 'reveal';
declare module 'react-reveal';
@@ -42,7 +42,7 @@ declare module '@react-pdf/renderer' {
* used as children of another react-pdf component. In addition, it should
* only have childs of type <Page />.
*/
- class Document extends React.Component<DocumentProps> { }
+ class Document extends React.Component<DocumentProps> {}
interface NodeProps {
style?: Style | Style[];
@@ -83,7 +83,7 @@ declare module '@react-pdf/renderer' {
* you want, but ensure not rendering a page inside any component besides
* Document.
*/
- class Page extends React.Component<PageProps> { }
+ class Page extends React.Component<PageProps> {}
interface ViewProps extends NodeProps {
/**
@@ -100,7 +100,7 @@ declare module '@react-pdf/renderer' {
* The most fundamental component for building a UI and is designed to be
* nested inside other views and can have 0 to many children.
*/
- class View extends React.Component<ViewProps> { }
+ class View extends React.Component<ViewProps> {}
interface ImageProps extends NodeProps {
debug?: boolean;
@@ -112,7 +112,7 @@ declare module '@react-pdf/renderer' {
* A React component for displaying network or local (Node only) JPG or
* PNG images, as well as base64 encoded image strings.
*/
- class Image extends React.Component<ImageProps> { }
+ class Image extends React.Component<ImageProps> {}
interface TextProps extends NodeProps {
/**
@@ -121,9 +121,7 @@ declare module '@react-pdf/renderer' {
*/
wrap?: boolean;
debug?: boolean;
- render?: (
- props: { pageNumber: number; totalPages: number },
- ) => React.ReactNode;
+ render?: (props: { pageNumber: number; totalPages: number }) => React.ReactNode;
children?: React.ReactNode;
/**
* How much hyphenated breaks should be avoided.
@@ -135,7 +133,7 @@ declare module '@react-pdf/renderer' {
* A React component for displaying text. Text supports nesting of other
* Text or Link components to create inline styling.
*/
- class Text extends React.Component<TextProps> { }
+ class Text extends React.Component<TextProps> {}
interface LinkProps extends NodeProps {
/**
@@ -152,13 +150,13 @@ declare module '@react-pdf/renderer' {
* A React component for displaying an hyperlink. Link’s can be nested
* inside a Text component, or being inside any other valid primitive.
*/
- class Link extends React.Component<LinkProps> { }
+ class Link extends React.Component<LinkProps> {}
interface NoteProps extends NodeProps {
children: string;
}
- class Note extends React.Component<NoteProps> { }
+ class Note extends React.Component<NoteProps> {}
interface BlobProviderParams {
blob: Blob | null;
@@ -177,7 +175,7 @@ declare module '@react-pdf/renderer' {
* @see https://react-pdf.org/advanced#on-the-fly-rendering
* @platform web
*/
- class BlobProvider extends React.Component<BlobProviderProps> { }
+ class BlobProvider extends React.Component<BlobProviderProps> {}
interface PDFViewerProps {
width?: number;
@@ -191,16 +189,14 @@ declare module '@react-pdf/renderer' {
* Iframe PDF viewer for client-side generated documents.
* @platform web
*/
- class PDFViewer extends React.Component<PDFViewerProps> { }
+ class PDFViewer extends React.Component<PDFViewerProps> {}
interface PDFDownloadLinkProps {
document: React.ReactElement<DocumentProps>;
fileName?: string;
style?: Style | Style[];
className?: string;
- children?:
- | React.ReactNode
- | ((params: BlobProviderParams) => React.ReactNode);
+ children?: React.ReactNode | ((params: BlobProviderParams) => React.ReactNode);
}
/**
@@ -208,7 +204,7 @@ declare module '@react-pdf/renderer' {
* @see https://react-pdf.org/advanced#on-the-fly-rendering
* @platform web
*/
- class PDFDownloadLink extends React.Component<PDFDownloadLinkProps> { }
+ class PDFDownloadLink extends React.Component<PDFDownloadLinkProps> {}
interface EmojiSource {
url: string;
@@ -221,28 +217,17 @@ declare module '@react-pdf/renderer' {
data: any;
[key: string]: any;
}
- type HyphenationCallback = (
- words: string[],
- glyphString: { [key: string]: any },
- ) => string[];
+ type HyphenationCallback = (words: string[], glyphString: { [key: string]: any }) => string[];
const Font: {
- register: (
- src: string,
- options: { family: string;[key: string]: any },
- ) => void;
+ register: (src: string, options: { family: string; [key: string]: any }) => void;
getEmojiSource: () => EmojiSource;
getRegisteredFonts: () => string[];
registerEmojiSource: (emojiSource: EmojiSource) => void;
- registerHyphenationCallback: (
- hyphenationCallback: HyphenationCallback,
- ) => void;
+ registerHyphenationCallback: (hyphenationCallback: HyphenationCallback) => void;
getHyphenationCallback: () => HyphenationCallback;
getFont: (fontFamily: string) => RegisteredFont | undefined;
- load: (
- fontFamily: string,
- document: React.ReactElement<DocumentProps>,
- ) => Promise<void>;
+ load: (fontFamily: string, document: React.ReactElement<DocumentProps>) => Promise<void>;
clear: () => void;
reset: () => void;
};
@@ -256,7 +241,7 @@ declare module '@react-pdf/renderer' {
width: number;
height: number;
orientation: Orientation;
- },
+ }
) => Style;
flatten: (...styles: Style[]) => Style;
absoluteFillObject: {
@@ -277,12 +262,10 @@ declare module '@react-pdf/renderer' {
type: string;
props: { [key: string]: any };
},
- root?: any,
+ root?: any
) => any;
- const pdf: (
- document: React.ReactElement<DocumentProps>,
- ) => {
+ const pdf: (document: React.ReactElement<DocumentProps>) => {
isDirty: () => boolean;
updateContainer: (document: React.ReactElement<any>) => void;
toBuffer: () => NodeJS.ReadableStream;
@@ -290,15 +273,9 @@ declare module '@react-pdf/renderer' {
toString: () => string;
};
- const renderToStream: (
- document: React.ReactElement<DocumentProps>,
- ) => NodeJS.ReadableStream;
+ const renderToStream: (document: React.ReactElement<DocumentProps>) => NodeJS.ReadableStream;
- const renderToFile: (
- document: React.ReactElement<DocumentProps>,
- filePath: string,
- callback?: (output: NodeJS.ReadableStream, filePath: string) => any,
- ) => Promise<NodeJS.ReadableStream>;
+ const renderToFile: (document: React.ReactElement<DocumentProps>, filePath: string, callback?: (output: NodeJS.ReadableStream, filePath: string) => any) => Promise<NodeJS.ReadableStream>;
const render: typeof renderToFile;
}
@@ -318,19 +295,5 @@ declare module '@react-pdf/renderer' {
const pdf: typeof ReactPDF.pdf;
export default ReactPDF;
- export {
- Document,
- Page,
- View,
- Image,
- Text,
- Link,
- Note,
- Font,
- StyleSheet,
- createInstance,
- PDFRenderer,
- version,
- pdf,
- };
-} \ No newline at end of file
+ export { Document, Page, View, Image, Text, Link, Note, Font, StyleSheet, createInstance, PDFRenderer, version, pdf };
+}
diff --git a/tsconfig.json b/tsconfig.json
index f688f18ea..993ab13b9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"target": "es5",
+ "downlevelIteration": true,
// "module": "system",
"removeComments": true,
"experimentalDecorators": true,
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index 83255a798..000000000
--- a/tslint.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "linterOptions": {
- "exclude": [
- "./src/client/northstar/**",
- "**/*.js"
- ]
- },
- "rules": {
- "no-return-await": true,
- "no-string-literal": true,
- "prefer-object-spread": true,
- "prefer-for-of": true,
- "no-unnecessary-type-assertion": true,
- "triple-equals": true,
- "prefer-const": true,
- "no-unnecessary-callback-wrapper": true,
- "class-name": true,
- "arrow-return-shorthand": true,
- "semicolon": [
- true,
- "always"
- ],
- "curly": [
- true,
- "ignore-same-line"
- ],
- "no-tautology-expression": true,
- "unnecessary-constructor": true
- },
- "defaultSeverity": "warning",
- "jsRules": {}
-} \ No newline at end of file
diff --git a/views/login.pug b/views/login.pug
index 98816e9c8..71c6933be 100644
--- a/views/login.pug
+++ b/views/login.pug
@@ -15,10 +15,10 @@ block content
h3.auth_header Log In
.form-group
.col-sm-7
- input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required)
+ input.form-control(type='email', name='email', id='email', placeholder='Email (or blank for "guest")', autofocus)
.form-group
.col-sm-7
- input.form-control(type='password', name='password', id='password', placeholder='Password', required)
+ input.form-control(type='password', name='password', id='password', placeholder='Password')
.form-group
.col-sm-offset-3.col-sm-7
button.btn.btn-success(id='submit', type='submit')
diff --git a/webpack.config.js b/webpack.config.js
index 3fd00bcf3..5a954db19 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -49,7 +49,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'],
- mobileInterface: ["./src/mobile/MobileMain.tsx", 'webpack-hot-middleware/client?reload=true'],
+ mobileInterface: ["./src/client/views/Main.tsx", 'webpack-hot-middleware/client?reload=true'],
},
devtool: "source-map",
output: {