aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormonoguitari <113245090+monoguitari@users.noreply.github.com>2023-08-22 14:15:04 -0400
committermonoguitari <113245090+monoguitari@users.noreply.github.com>2023-08-22 14:15:04 -0400
commit9293fd8c4128b41b31f9b2214d6799fdff0f2aaa (patch)
tree45809c42545b10515f6f88065318b454549dacd1
parent347e8e2bd32854b36828b7bcc645c9c361204251 (diff)
parent1c52bd054385d2584bbeae41eecdf9ba6999c25f (diff)
Merge branch 'master' into advanced-trails
-rw-r--r--.vscode/launch.json10
-rw-r--r--deploy/index.html7
-rw-r--r--deploy/loader.css5
-rw-r--r--package-lock.json4998
-rw-r--r--package.json11
-rw-r--r--report.20230313.165455.65490.0.001.json1294
-rw-r--r--src/.DS_Storebin10244 -> 10244 bytes
-rw-r--r--src/Utils.ts34
-rw-r--r--src/client/DocServer.ts88
-rw-r--r--src/client/Network.ts17
-rw-r--r--src/client/documents/DocumentTypes.ts7
-rw-r--r--src/client/documents/Documents.ts313
-rw-r--r--src/client/theme.ts (renamed from src/fields/ListSpec.ts)0
-rw-r--r--src/client/util/CaptureManager.scss18
-rw-r--r--src/client/util/CaptureManager.tsx1
-rw-r--r--src/client/util/CurrentUserUtils.ts367
-rw-r--r--src/client/util/DocumentManager.ts83
-rw-r--r--src/client/util/DragManager.ts44
-rw-r--r--src/client/util/DropConverter.ts10
-rw-r--r--src/client/util/GroupManager.scss78
-rw-r--r--src/client/util/GroupManager.tsx109
-rw-r--r--src/client/util/GroupMemberView.scss33
-rw-r--r--src/client/util/GroupMemberView.tsx124
-rw-r--r--src/client/util/InteractionUtils.tsx2
-rw-r--r--src/client/util/LinkFollower.ts16
-rw-r--r--src/client/util/LinkManager.ts77
-rw-r--r--src/client/util/PingManager.ts39
-rw-r--r--src/client/util/RTFMarkup.tsx5
-rw-r--r--src/client/util/ReplayMovements.ts1
-rw-r--r--src/client/util/ReportManager.scss88
-rw-r--r--src/client/util/ReportManager.tsx297
-rw-r--r--src/client/util/ScriptManager.ts38
-rw-r--r--src/client/util/SelectionManager.ts3
-rw-r--r--src/client/util/ServerStats.tsx26
-rw-r--r--src/client/util/SettingsManager.scss141
-rw-r--r--src/client/util/SettingsManager.tsx458
-rw-r--r--src/client/util/SharingManager.scss254
-rw-r--r--src/client/util/SharingManager.tsx432
-rw-r--r--src/client/util/UndoManager.ts29
-rw-r--r--src/client/util/reportManager/ReportManager.scss364
-rw-r--r--src/client/util/reportManager/ReportManager.tsx406
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx381
-rw-r--r--src/client/util/reportManager/reportManagerSchema.ts877
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts254
-rw-r--r--src/client/views/AntimodeMenu.scss32
-rw-r--r--src/client/views/AntimodeMenu.tsx5
-rw-r--r--src/client/views/AudioWaveform.tsx6
-rw-r--r--src/client/views/ContextMenu.scss24
-rw-r--r--src/client/views/ContextMenu.tsx17
-rw-r--r--src/client/views/ContextMenuItem.tsx13
-rw-r--r--src/client/views/DashboardView.scss112
-rw-r--r--src/client/views/DashboardView.tsx152
-rw-r--r--src/client/views/DocComponent.tsx53
-rw-r--r--src/client/views/DocumentButtonBar.tsx8
-rw-r--r--src/client/views/DocumentDecorations.scss103
-rw-r--r--src/client/views/DocumentDecorations.tsx131
-rw-r--r--src/client/views/EditableView.tsx36
-rw-r--r--src/client/views/FilterPanel.scss23
-rw-r--r--src/client/views/FilterPanel.tsx32
-rw-r--r--src/client/views/GestureOverlay.tsx106
-rw-r--r--src/client/views/GlobalKeyHandler.ts32
-rw-r--r--src/client/views/InkControlPtHandles.tsx34
-rw-r--r--src/client/views/InkStroke.scss1
-rw-r--r--src/client/views/InkStrokeProperties.ts1
-rw-r--r--src/client/views/InkTranscription.tsx6
-rw-r--r--src/client/views/InkingStroke.tsx65
-rw-r--r--src/client/views/LightboxView.tsx6
-rw-r--r--src/client/views/Main.scss1
-rw-r--r--src/client/views/Main.tsx17
-rw-r--r--src/client/views/MainView.scss17
-rw-r--r--src/client/views/MainView.tsx80
-rw-r--r--src/client/views/MainViewModal.scss6
-rw-r--r--src/client/views/MainViewModal.tsx7
-rw-r--r--src/client/views/MarqueeAnnotator.tsx8
-rw-r--r--src/client/views/OverlayView.scss5
-rw-r--r--src/client/views/Palette.scss30
-rw-r--r--src/client/views/Palette.tsx69
-rw-r--r--src/client/views/PreviewCursor.tsx26
-rw-r--r--src/client/views/PropertiesButtons.scss96
-rw-r--r--src/client/views/PropertiesButtons.tsx444
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.scss2
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx2
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx7
-rw-r--r--src/client/views/PropertiesSection.scss24
-rw-r--r--src/client/views/PropertiesSection.tsx64
-rw-r--r--src/client/views/PropertiesView.scss536
-rw-r--r--src/client/views/PropertiesView.tsx1055
-rw-r--r--src/client/views/ScriptingRepl.tsx3
-rw-r--r--src/client/views/StyleProvider.scss4
-rw-r--r--src/client/views/StyleProvider.tsx67
-rw-r--r--src/client/views/TemplateMenu.tsx7
-rw-r--r--src/client/views/UndoStack.scss9
-rw-r--r--src/client/views/UndoStack.tsx61
-rw-r--r--src/client/views/animationtimeline/Keyframe.tsx10
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx2
-rw-r--r--src/client/views/animationtimeline/Track.tsx42
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx4
-rw-r--r--src/client/views/collections/CollectionDockingView.scss14
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx66
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx50
-rw-r--r--src/client/views/collections/CollectionMenu.scss653
-rw-r--r--src/client/views/collections/CollectionMenu.tsx77
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx24
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx1
-rw-r--r--src/client/views/collections/CollectionPileView.tsx22
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx42
-rw-r--r--src/client/views/collections/CollectionStackingView.scss1
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx59
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx1
-rw-r--r--src/client/views/collections/CollectionSubView.tsx32
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx8
-rw-r--r--src/client/views/collections/CollectionTreeView.scss2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx55
-rw-r--r--src/client/views/collections/CollectionView.tsx7
-rw-r--r--src/client/views/collections/TabDocView.scss17
-rw-r--r--src/client/views/collections/TabDocView.tsx198
-rw-r--r--src/client/views/collections/TreeView.scss47
-rw-r--r--src/client/views/collections/TreeView.tsx236
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss7
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx130
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx69
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx73
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx2
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss7
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx119
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss9
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx36
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss10
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx37
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx7
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx8
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx4
-rw-r--r--src/client/views/global/globalCssVariables.scss4
-rw-r--r--src/client/views/global/globalScripts.ts416
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx6
-rw-r--r--src/client/views/linking/LinkPopup.scss5
-rw-r--r--src/client/views/linking/LinkPopup.tsx5
-rw-r--r--src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss15
-rw-r--r--src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx50
-rw-r--r--src/client/views/newlightbox/ButtonMenu/index.ts1
-rw-r--r--src/client/views/newlightbox/ButtonMenu/utils.ts3
-rw-r--r--src/client/views/newlightbox/ExploreView/ExploreView.scss44
-rw-r--r--src/client/views/newlightbox/ExploreView/ExploreView.tsx32
-rw-r--r--src/client/views/newlightbox/ExploreView/index.ts1
-rw-r--r--src/client/views/newlightbox/ExploreView/utils.ts20
-rw-r--r--src/client/views/newlightbox/Header/LightboxHeader.scss71
-rw-r--r--src/client/views/newlightbox/Header/LightboxHeader.tsx62
-rw-r--r--src/client/views/newlightbox/Header/index.ts1
-rw-r--r--src/client/views/newlightbox/Header/utils.ts4
-rw-r--r--src/client/views/newlightbox/NewLightboxStyles.scss73
-rw-r--r--src/client/views/newlightbox/NewLightboxView.scss34
-rw-r--r--src/client/views/newlightbox/NewLightboxView.tsx388
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.scss117
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.tsx196
-rw-r--r--src/client/views/newlightbox/RecommendationList/index.ts1
-rw-r--r--src/client/views/newlightbox/RecommendationList/utils.ts9
-rw-r--r--src/client/views/newlightbox/components/EditableText/EditableText.scss34
-rw-r--r--src/client/views/newlightbox/components/EditableText/EditableText.tsx65
-rw-r--r--src/client/views/newlightbox/components/EditableText/index.ts1
-rw-r--r--src/client/views/newlightbox/components/Recommendation/Recommendation.scss176
-rw-r--r--src/client/views/newlightbox/components/Recommendation/Recommendation.tsx102
-rw-r--r--src/client/views/newlightbox/components/Recommendation/index.ts2
-rw-r--r--src/client/views/newlightbox/components/Recommendation/utils.ts23
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss82
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx22
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/index.ts1
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/utils.ts5
-rw-r--r--src/client/views/newlightbox/components/Template/Template.scss15
-rw-r--r--src/client/views/newlightbox/components/Template/Template.tsx10
-rw-r--r--src/client/views/newlightbox/components/Template/index.ts1
-rw-r--r--src/client/views/newlightbox/components/Template/utils.ts3
-rw-r--r--src/client/views/newlightbox/components/index.ts3
-rw-r--r--src/client/views/newlightbox/utils.ts121
-rw-r--r--src/client/views/nodes/AudioBox.tsx15
-rw-r--r--src/client/views/nodes/ColorBox.tsx9
-rw-r--r--src/client/views/nodes/ComparisonBox.scss2
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx89
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss12
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx168
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss43
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx523
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx114
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx399
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx244
-rw-r--r--src/client/views/nodes/DataVizBox/utils/D3Utils.ts1
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.scss7
-rw-r--r--src/client/views/nodes/DocumentView.tsx209
-rw-r--r--src/client/views/nodes/EquationBox.scss9
-rw-r--r--src/client/views/nodes/EquationBox.tsx7
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/FontIconBox/ButtonInterface.ts (renamed from src/client/views/nodes/button/ButtonInterface.ts)0
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBadge.scss (renamed from src/client/views/nodes/button/FontIconBadge.scss)5
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBadge.tsx (renamed from src/client/views/nodes/button/FontIconBadge.tsx)0
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.scss (renamed from src/client/views/nodes/button/FontIconBox.scss)4
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx409
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx11
-rw-r--r--src/client/views/nodes/ImageBox.tsx49
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx25
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx15
-rw-r--r--src/client/views/nodes/LabelBox.tsx2
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx2
-rw-r--r--src/client/views/nodes/LinkBox.scss8
-rw-r--r--src/client/views/nodes/LinkBox.tsx2
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx2
-rw-r--r--src/client/views/nodes/LoadingBox.scss4
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx8
-rw-r--r--src/client/views/nodes/MapBox/MapBox2.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.tsx14
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx63
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx44
-rw-r--r--src/client/views/nodes/ScreenshotBox.scss36
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx33
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx10
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
-rw-r--r--src/client/views/nodes/WebBox.tsx44
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx953
-rw-r--r--src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx77
-rw-r--r--src/client/views/nodes/button/colorDropdown/index.ts1
-rw-r--r--src/client/views/nodes/button/textButton/TextButton.tsx30
-rw-r--r--src/client/views/nodes/button/textButton/index.ts1
-rw-r--r--src/client/views/nodes/button/toggleButton/ToggleButton.tsx34
-rw-r--r--src/client/views/nodes/button/toggleButton/index.ts1
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss20
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx240
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts18
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx2
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts26
-rw-r--r--src/client/views/nodes/importBox/ImportElementBox.tsx38
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx586
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx73
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx186
-rw-r--r--src/client/views/pdf/Annotation.tsx4
-rw-r--r--src/client/views/pdf/PDFViewer.scss8
-rw-r--r--src/client/views/pdf/PDFViewer.tsx41
-rw-r--r--src/client/views/search/SearchBox.scss64
-rw-r--r--src/client/views/search/SearchBox.tsx73
-rw-r--r--src/client/views/selectedDoc/SelectedDocView.scss3
-rw-r--r--src/client/views/selectedDoc/SelectedDocView.tsx47
-rw-r--r--src/client/views/selectedDoc/index.ts1
-rw-r--r--src/client/views/topbar/TopBar.scss417
-rw-r--r--src/client/views/topbar/TopBar.tsx113
-rw-r--r--src/fields/CursorField.ts41
-rw-r--r--src/fields/Doc.ts173
-rw-r--r--src/fields/DocSymbols.ts5
-rw-r--r--src/fields/FieldLoader.tsx5
-rw-r--r--src/fields/FieldSymbols.ts2
-rw-r--r--src/fields/IconField.ts26
-rw-r--r--src/fields/List.ts463
-rw-r--r--src/fields/ObjectField.ts15
-rw-r--r--src/fields/PresField.ts6
-rw-r--r--src/fields/Proxy.ts4
-rw-r--r--src/fields/SchemaHeaderField.ts14
-rw-r--r--src/fields/Types.ts9
-rw-r--r--src/fields/documentSchemas.ts20
-rw-r--r--src/fields/util.ts329
-rw-r--r--src/mobile/MobileMain.tsx2
-rw-r--r--src/scraping/buxton/narratives/Theme - Chord Kbds.docxbin5701815 -> 0 bytes
-rw-r--r--src/server/ApiManagers/AzureManager.ts67
-rw-r--r--src/server/ApiManagers/UploadManager.ts9
-rw-r--r--src/server/ApiManagers/UserManager.ts16
-rw-r--r--src/server/DashSession/Session/utilities/session_config.ts2
-rw-r--r--src/server/DashUploadUtils.ts71
-rw-r--r--src/server/server_Initialization.ts32
268 files changed, 18946 insertions, 8002 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index ce9f50f67..9ba624af9 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -17,6 +17,16 @@
{
"type": "chrome",
"request": "launch",
+ "name": "Launch Brave against localhost",
+ "sourceMaps": true,
+ "url": "http://localhost:1050/home",
+ "runtimeExecutable": "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
+ "webRoot": "${workspaceFolder}",
+ "runtimeArgs": ["--experimental-modules"]
+ },
+ {
+ "type": "chrome",
+ "request": "launch",
"name": "Launch Chromium against localhost",
"sourceMaps": true,
"breakOnLoad": true,
diff --git a/deploy/index.html b/deploy/index.html
index f8003f126..c8ad2a8d4 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -5,7 +5,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
- href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
+ href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&family=Nunito:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;0,1000;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900;1,1000&family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
rel="stylesheet">
<link rel="shortcut icon" type="image/jpg" href="./assets/favicon.png" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
@@ -67,8 +67,9 @@
}
.dash-animation-container {
- width: 10vw;
- height: 10vw;
+ width: fit-content;
+ height: fit-content;
+ padding: 20px;
display: flex;
align-items: center;
justify-content: center;
diff --git a/deploy/loader.css b/deploy/loader.css
index 4be0cc98c..4d5e04d71 100644
--- a/deploy/loader.css
+++ b/deploy/loader.css
@@ -36,8 +36,9 @@
}
.dash-animation-container {
- width: 10vw;
- height: 10vw;
+ width: fit-content;
+ height: fit-content;
+ padding: 20px;
display: flex;
align-items: center;
justify-content: center;
diff --git a/package-lock.json b/package-lock.json
index 4da0600b0..e6985cf65 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,21 +4,260 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
- "@babel/code-frame": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
- "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "@ampproject/remapping": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+ "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@azure/abort-controller": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
+ "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
+ "requires": {
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-auth": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz",
+ "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-http": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-3.0.2.tgz",
+ "integrity": "sha512-o1wR9JrmoM0xEAa0Ue7Sp8j+uJvmqYaGoHOCT5qaVYmvgmnZDC0OvQimPA/JR3u77Sz6D1y3Xmk1y69cDU9q9A==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-auth": "^1.3.0",
+ "@azure/core-tracing": "1.0.0-preview.13",
+ "@azure/core-util": "^1.1.1",
+ "@azure/logger": "^1.0.0",
+ "@types/node-fetch": "^2.5.0",
+ "@types/tunnel": "^0.0.3",
+ "form-data": "^4.0.0",
+ "node-fetch": "^2.6.7",
+ "process": "^0.11.10",
+ "tslib": "^2.2.0",
+ "tunnel": "^0.0.6",
+ "uuid": "^8.3.0",
+ "xml2js": "^0.5.0"
+ },
+ "dependencies": {
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ }
+ }
+ },
+ "@azure/core-lro": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.3.tgz",
+ "integrity": "sha512-ubkOf2YCnVtq7KqEJQqAI8dDD5rH1M6OP5kW0KO/JQyTaxLA0N0pjFWvvaysCj9eHMNBcuuoZXhhl0ypjod2DA==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-util": "^1.2.0",
+ "@azure/logger": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-paging": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz",
+ "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==",
+ "requires": {
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-tracing": {
+ "version": "1.0.0-preview.13",
+ "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz",
+ "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==",
+ "requires": {
+ "@opentelemetry/api": "^1.0.1",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-util": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz",
+ "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/logger": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz",
+ "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==",
"requires": {
- "@babel/highlight": "^7.18.6"
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/storage-blob": {
+ "version": "12.14.0",
+ "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.14.0.tgz",
+ "integrity": "sha512-g8GNUDpMisGXzBeD+sKphhH5yLwesB4JkHr1U6be/X3F+cAMcyGLPD1P89g2M7wbEtUJWoikry1rlr83nNRBzg==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-http": "^3.0.0",
+ "@azure/core-lro": "^2.2.0",
+ "@azure/core-paging": "^1.1.1",
+ "@azure/core-tracing": "1.0.0-preview.13",
+ "@azure/logger": "^1.0.0",
+ "events": "^3.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
+ "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
+ "requires": {
+ "@babel/highlight": "^7.22.5"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz",
+ "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ=="
+ },
+ "@babel/core": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz",
+ "integrity": "sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==",
+ "requires": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.22.10",
+ "@babel/generator": "^7.22.10",
+ "@babel/helper-compilation-targets": "^7.22.10",
+ "@babel/helper-module-transforms": "^7.22.9",
+ "@babel/helpers": "^7.22.10",
+ "@babel/parser": "^7.22.10",
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.10",
+ "@babel/types": "^7.22.10",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.2",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
+ "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
+ "requires": {
+ "@babel/highlight": "^7.22.10",
+ "chalk": "^2.4.2"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
+ "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
+ "requires": {
+ "@babel/types": "^7.22.10",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
+ "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
+ "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ=="
+ },
+ "@babel/traverse": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz",
+ "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==",
+ "requires": {
+ "@babel/code-frame": "^7.22.10",
+ "@babel/generator": "^7.22.10",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.22.10",
+ "@babel/types": "^7.22.10",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
+ "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+ }
}
},
"@babel/generator": {
- "version": "7.20.14",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz",
- "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==",
+ "version": "7.22.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.7.tgz",
+ "integrity": "sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==",
"requires": {
- "@babel/types": "^7.20.7",
+ "@babel/types": "^7.22.5",
"@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
@@ -30,34 +269,207 @@
"@babel/types": "^7.18.6"
}
},
+ "@babel/helper-builder-binary-assignment-operator-visitor": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz",
+ "integrity": "sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==",
+ "requires": {
+ "@babel/types": "^7.22.10"
+ },
+ "dependencies": {
+ "@babel/types": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
+ "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz",
+ "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==",
+ "requires": {
+ "@babel/compat-data": "^7.22.9",
+ "@babel/helper-validator-option": "^7.22.5",
+ "browserslist": "^4.21.9",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "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"
+ }
+ },
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ }
+ }
+ },
+ "@babel/helper-create-class-features-plugin": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz",
+ "integrity": "sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-member-expression-to-functions": "^7.22.5",
+ "@babel/helper-optimise-call-expression": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.9",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+ }
+ }
+ },
+ "@babel/helper-create-regexp-features-plugin": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz",
+ "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "regexpu-core": "^5.3.1",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+ }
+ }
+ },
+ "@babel/helper-define-polyfill-provider": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz",
+ "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==",
+ "requires": {
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ }
+ }
+ },
"@babel/helper-environment-visitor": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
- "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg=="
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
+ "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q=="
},
"@babel/helper-function-name": {
- "version": "7.19.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
- "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
+ "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
"requires": {
- "@babel/template": "^7.18.10",
- "@babel/types": "^7.19.0"
+ "@babel/template": "^7.22.5",
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-hoist-variables": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
- "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+ "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"requires": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz",
+ "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==",
+ "requires": {
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-module-imports": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
- "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz",
+ "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==",
"requires": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz",
+ "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==",
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-simple-access": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-validator-identifier": "^7.22.5"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
+ "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
+ "requires": {
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-plugin-utils": {
@@ -65,38 +477,357 @@
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz",
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ=="
},
+ "@babel/helper-remap-async-to-generator": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz",
+ "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-wrap-function": "^7.22.9"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ }
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz",
+ "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==",
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-member-expression-to-functions": "^7.22.5",
+ "@babel/helper-optimise-call-expression": "^7.22.5"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+ "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
+ "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
"@babel/helper-split-export-declaration": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
- "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+ "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"requires": {
- "@babel/types": "^7.18.6"
+ "@babel/types": "^7.22.5"
}
},
"@babel/helper-string-parser": {
- "version": "7.19.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
- "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw=="
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
+ "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw=="
},
"@babel/helper-validator-identifier": {
- "version": "7.19.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
- "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
+ "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ=="
+ },
+ "@babel/helper-validator-option": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
+ "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw=="
+ },
+ "@babel/helper-wrap-function": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz",
+ "integrity": "sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==",
+ "requires": {
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/template": "^7.22.5",
+ "@babel/types": "^7.22.10"
+ },
+ "dependencies": {
+ "@babel/types": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
+ "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "to-fast-properties": "^2.0.0"
+ }
+ }
+ }
+ },
+ "@babel/helpers": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz",
+ "integrity": "sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==",
+ "requires": {
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.10",
+ "@babel/types": "^7.22.10"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
+ "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
+ "requires": {
+ "@babel/highlight": "^7.22.10",
+ "chalk": "^2.4.2"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
+ "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
+ "requires": {
+ "@babel/types": "^7.22.10",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
+ "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
+ "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ=="
+ },
+ "@babel/traverse": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz",
+ "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==",
+ "requires": {
+ "@babel/code-frame": "^7.22.10",
+ "@babel/generator": "^7.22.10",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.22.10",
+ "@babel/types": "^7.22.10",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
+ "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "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=="
+ }
+ }
},
"@babel/highlight": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
- "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
+ "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
"requires": {
- "@babel/helper-validator-identifier": "^7.18.6",
+ "@babel/helper-validator-identifier": "^7.22.5",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
- "version": "7.20.15",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz",
- "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg=="
+ "version": "7.22.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
+ "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q=="
+ },
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz",
+ "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz",
+ "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-transform-optional-chaining": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-external-helpers": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz",
+ "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-proposal-class-properties": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
+ "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==",
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ }
+ },
+ "@babel/plugin-proposal-object-rest-spread": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz",
+ "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==",
+ "requires": {
+ "@babel/compat-data": "^7.20.5",
+ "@babel/helper-compilation-targets": "^7.20.7",
+ "@babel/helper-plugin-utils": "^7.20.2",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.20.7"
+ }
+ },
+ "@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="
+ },
+ "@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ }
+ },
+ "@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-dynamic-import": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-export-namespace-from": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
+ "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-import-assertions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz",
+ "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-syntax-import-attributes": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz",
+ "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
},
"@babel/plugin-syntax-jsx": {
"version": "7.18.6",
@@ -106,6 +837,1161 @@
"@babel/helper-plugin-utils": "^7.18.6"
}
},
+ "@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-typescript": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz",
+ "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-syntax-unicode-sets-regex": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+ "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ }
+ },
+ "@babel/plugin-transform-arrow-functions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz",
+ "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-async-generator-functions": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz",
+ "integrity": "sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==",
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.9",
+ "@babel/plugin-syntax-async-generators": "^7.8.4"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-async-to-generator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz",
+ "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz",
+ "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-block-scoping": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz",
+ "integrity": "sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-class-properties": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz",
+ "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==",
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-class-static-block": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz",
+ "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==",
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-classes": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz",
+ "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-optimise-call-expression": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "globals": "^11.1.0"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-computed-properties": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz",
+ "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/template": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz",
+ "integrity": "sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-dotall-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz",
+ "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==",
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-duplicate-keys": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz",
+ "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-dynamic-import": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz",
+ "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz",
+ "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==",
+ "requires": {
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-export-namespace-from": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz",
+ "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-for-of": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz",
+ "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-function-name": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz",
+ "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==",
+ "requires": {
+ "@babel/helper-compilation-targets": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-json-strings": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz",
+ "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-json-strings": "^7.8.3"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz",
+ "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz",
+ "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-member-expression-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz",
+ "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-modules-amd": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz",
+ "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==",
+ "requires": {
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-modules-commonjs": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz",
+ "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==",
+ "requires": {
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-simple-access": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-modules-systemjs": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz",
+ "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==",
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-modules-umd": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz",
+ "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==",
+ "requires": {
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz",
+ "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==",
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-new-target": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz",
+ "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz",
+ "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-numeric-separator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz",
+ "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-object-rest-spread": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz",
+ "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==",
+ "requires": {
+ "@babel/compat-data": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-object-super": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz",
+ "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz",
+ "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-optional-chaining": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz",
+ "integrity": "sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-parameters": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz",
+ "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-private-methods": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz",
+ "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==",
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-private-property-in-object": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz",
+ "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-property-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz",
+ "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-react-display-name": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz",
+ "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-react-jsx": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz",
+ "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-jsx": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ },
+ "@babel/plugin-syntax-jsx": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
+ "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ }
+ }
+ },
+ "@babel/plugin-transform-react-jsx-development": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz",
+ "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==",
+ "requires": {
+ "@babel/plugin-transform-react-jsx": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-react-pure-annotations": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz",
+ "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-regenerator": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz",
+ "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "regenerator-transform": "^0.15.2"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-reserved-words": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz",
+ "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-shorthand-properties": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz",
+ "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-spread": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz",
+ "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-sticky-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz",
+ "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-template-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz",
+ "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-typeof-symbol": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz",
+ "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-typescript": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.10.tgz",
+ "integrity": "sha512-7++c8I/ymsDo4QQBAgbraXLzIM6jmfao11KgIBEYZRReWzNWH9NtNgJcyrZiXsOPh523FQm6LfpLyy/U5fn46A==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-create-class-features-plugin": "^7.22.10",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-typescript": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-unicode-escapes": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz",
+ "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz",
+ "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==",
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-unicode-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz",
+ "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==",
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz",
+ "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==",
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/preset-env": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.10.tgz",
+ "integrity": "sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A==",
+ "requires": {
+ "@babel/compat-data": "^7.22.9",
+ "@babel/helper-compilation-targets": "^7.22.10",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.5",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5",
+ "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+ "@babel/plugin-syntax-import-assertions": "^7.22.5",
+ "@babel/plugin-syntax-import-attributes": "^7.22.5",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5",
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.22.5",
+ "@babel/plugin-transform-async-generator-functions": "^7.22.10",
+ "@babel/plugin-transform-async-to-generator": "^7.22.5",
+ "@babel/plugin-transform-block-scoped-functions": "^7.22.5",
+ "@babel/plugin-transform-block-scoping": "^7.22.10",
+ "@babel/plugin-transform-class-properties": "^7.22.5",
+ "@babel/plugin-transform-class-static-block": "^7.22.5",
+ "@babel/plugin-transform-classes": "^7.22.6",
+ "@babel/plugin-transform-computed-properties": "^7.22.5",
+ "@babel/plugin-transform-destructuring": "^7.22.10",
+ "@babel/plugin-transform-dotall-regex": "^7.22.5",
+ "@babel/plugin-transform-duplicate-keys": "^7.22.5",
+ "@babel/plugin-transform-dynamic-import": "^7.22.5",
+ "@babel/plugin-transform-exponentiation-operator": "^7.22.5",
+ "@babel/plugin-transform-export-namespace-from": "^7.22.5",
+ "@babel/plugin-transform-for-of": "^7.22.5",
+ "@babel/plugin-transform-function-name": "^7.22.5",
+ "@babel/plugin-transform-json-strings": "^7.22.5",
+ "@babel/plugin-transform-literals": "^7.22.5",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.22.5",
+ "@babel/plugin-transform-member-expression-literals": "^7.22.5",
+ "@babel/plugin-transform-modules-amd": "^7.22.5",
+ "@babel/plugin-transform-modules-commonjs": "^7.22.5",
+ "@babel/plugin-transform-modules-systemjs": "^7.22.5",
+ "@babel/plugin-transform-modules-umd": "^7.22.5",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
+ "@babel/plugin-transform-new-target": "^7.22.5",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5",
+ "@babel/plugin-transform-numeric-separator": "^7.22.5",
+ "@babel/plugin-transform-object-rest-spread": "^7.22.5",
+ "@babel/plugin-transform-object-super": "^7.22.5",
+ "@babel/plugin-transform-optional-catch-binding": "^7.22.5",
+ "@babel/plugin-transform-optional-chaining": "^7.22.10",
+ "@babel/plugin-transform-parameters": "^7.22.5",
+ "@babel/plugin-transform-private-methods": "^7.22.5",
+ "@babel/plugin-transform-private-property-in-object": "^7.22.5",
+ "@babel/plugin-transform-property-literals": "^7.22.5",
+ "@babel/plugin-transform-regenerator": "^7.22.10",
+ "@babel/plugin-transform-reserved-words": "^7.22.5",
+ "@babel/plugin-transform-shorthand-properties": "^7.22.5",
+ "@babel/plugin-transform-spread": "^7.22.5",
+ "@babel/plugin-transform-sticky-regex": "^7.22.5",
+ "@babel/plugin-transform-template-literals": "^7.22.5",
+ "@babel/plugin-transform-typeof-symbol": "^7.22.5",
+ "@babel/plugin-transform-unicode-escapes": "^7.22.10",
+ "@babel/plugin-transform-unicode-property-regex": "^7.22.5",
+ "@babel/plugin-transform-unicode-regex": "^7.22.5",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.22.5",
+ "@babel/preset-modules": "0.1.6-no-external-plugins",
+ "@babel/types": "^7.22.10",
+ "babel-plugin-polyfill-corejs2": "^0.4.5",
+ "babel-plugin-polyfill-corejs3": "^0.8.3",
+ "babel-plugin-polyfill-regenerator": "^0.5.2",
+ "core-js-compat": "^3.31.0",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ },
+ "@babel/types": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
+ "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+ }
+ }
+ },
+ "@babel/preset-modules": {
+ "version": "0.1.6-no-external-plugins",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
+ "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
+ }
+ },
+ "@babel/preset-react": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz",
+ "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.5",
+ "@babel/plugin-transform-react-display-name": "^7.22.5",
+ "@babel/plugin-transform-react-jsx": "^7.22.5",
+ "@babel/plugin-transform-react-jsx-development": "^7.22.5",
+ "@babel/plugin-transform-react-pure-annotations": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ }
+ }
+ },
+ "@babel/preset-typescript": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz",
+ "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.5",
+ "@babel/plugin-syntax-jsx": "^7.22.5",
+ "@babel/plugin-transform-modules-commonjs": "^7.22.5",
+ "@babel/plugin-transform-typescript": "^7.22.5"
+ },
+ "dependencies": {
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg=="
+ },
+ "@babel/plugin-syntax-jsx": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
+ "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ }
+ }
+ },
+ "@babel/regjsgen": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz",
+ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
+ },
"@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
@@ -124,28 +2010,28 @@
}
},
"@babel/template": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
- "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
+ "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
"requires": {
- "@babel/code-frame": "^7.18.6",
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7"
+ "@babel/code-frame": "^7.22.5",
+ "@babel/parser": "^7.22.5",
+ "@babel/types": "^7.22.5"
}
},
"@babel/traverse": {
- "version": "7.20.13",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
- "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
- "requires": {
- "@babel/code-frame": "^7.18.6",
- "@babel/generator": "^7.20.7",
- "@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-function-name": "^7.19.0",
- "@babel/helper-hoist-variables": "^7.18.6",
- "@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/parser": "^7.20.13",
- "@babel/types": "^7.20.7",
+ "version": "7.22.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
+ "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
+ "requires": {
+ "@babel/code-frame": "^7.22.5",
+ "@babel/generator": "^7.22.7",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.22.7",
+ "@babel/types": "^7.22.5",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -166,12 +2052,12 @@
}
},
"@babel/types": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
- "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz",
+ "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==",
"requires": {
- "@babel/helper-string-parser": "^7.19.4",
- "@babel/helper-validator-identifier": "^7.19.1",
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
"to-fast-properties": "^2.0.0"
}
},
@@ -283,6 +2169,11 @@
"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.1.3",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz",
+ "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA=="
}
}
},
@@ -392,6 +2283,11 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
+ },
+ "stylis": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz",
+ "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA=="
}
}
},
@@ -1064,6 +2960,13 @@
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0",
"react-transition-group": "^4.4.0"
+ },
+ "dependencies": {
+ "popper.js": {
+ "version": "1.16.1-lts",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
+ "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
+ }
}
},
"@material-ui/styles": {
@@ -1298,6 +3201,30 @@
}
}
},
+ "@mui/styled-engine-sc": {
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.12.0.tgz",
+ "integrity": "sha512-3MgYoY2YG5tx0E5oKqvCv94oL0ABVBr+qpcyvciXW/v0wzPG6bXvuZV80GHYlJfasgnnRa1AbRWf5a9FcX8v6g==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "prop-types": "^15.8.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
+ "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
+ "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
+ }
+ }
+ },
"@mui/system": {
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.1.tgz",
@@ -1368,6 +3295,12 @@
}
}
},
+ "@nicolo-ribaudo/chokidar-2": {
+ "version": "2.1.8-no-fsevents.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
+ "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==",
+ "optional": true
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1472,6 +3405,11 @@
"@octokit/openapi-types": "^16.0.0"
}
},
+ "@opentelemetry/api": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz",
+ "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA=="
+ },
"@popperjs/core": {
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
@@ -2320,8 +4258,28 @@
"@types/node": {
"version": "10.17.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
- "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
- "dev": true
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="
+ },
+ "@types/node-fetch": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz",
+ "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==",
+ "requires": {
+ "@types/node": "*",
+ "form-data": "^3.0.0"
+ },
+ "dependencies": {
+ "form-data": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+ "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ }
+ }
},
"@types/nodemailer": {
"version": "4.6.8",
@@ -2583,14 +4541,6 @@
"@types/react": "*",
"date-fns": "^2.0.1",
"popper.js": "^1.14.1"
- },
- "dependencies": {
- "popper.js": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
- "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
- "dev": true
- }
}
},
"@types/react-dom": {
@@ -2620,6 +4570,14 @@
"react-icons": "*"
}
},
+ "@types/react-is": {
+ "version": "18.2.1",
+ "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz",
+ "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-measure": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/@types/react-measure/-/react-measure-2.0.8.tgz",
@@ -2819,7 +4777,7 @@
"@types/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
+ "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=",
"dev": true
},
"@types/strip-json-comments": {
@@ -2828,6 +4786,11 @@
"integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
"dev": true
},
+ "@types/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw=="
+ },
"@types/supercluster": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.0.tgz",
@@ -2852,6 +4815,14 @@
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
"integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw=="
},
+ "@types/tunnel": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
+ "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==",
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/typescript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/typescript/-/typescript-2.0.0.tgz",
@@ -3170,7 +5141,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=="
}
}
},
@@ -3269,7 +5240,7 @@
"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.2",
@@ -3871,6 +5842,11 @@
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
+ "attr-accept": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+ "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+ },
"available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -4102,6 +6078,40 @@
"resolve": "^1.12.0"
}
},
+ "babel-plugin-polyfill-corejs2": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz",
+ "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==",
+ "requires": {
+ "@babel/compat-data": "^7.22.6",
+ "@babel/helper-define-polyfill-provider": "^0.4.2",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+ }
+ }
+ },
+ "babel-plugin-polyfill-corejs3": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz",
+ "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==",
+ "requires": {
+ "@babel/helper-define-polyfill-provider": "^0.4.2",
+ "core-js-compat": "^3.31.0"
+ }
+ },
+ "babel-plugin-polyfill-regenerator": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz",
+ "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==",
+ "requires": {
+ "@babel/helper-define-polyfill-provider": "^0.4.2"
+ }
+ },
"babel-plugin-styled-components": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz",
@@ -4166,7 +6176,7 @@
"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=="
},
"bail": {
"version": "2.0.2",
@@ -4236,7 +6246,7 @@
"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.5.1",
@@ -4497,16 +6507,341 @@
}
},
"browndash-components": {
- "version": "0.0.28",
- "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.28.tgz",
- "integrity": "sha512-pg8qAmaILk6GpUfrdDub6Bg5+LD7547UDzw44yntKsNQC8FDNE9KkDa1ShH5yAn+cwKuTMa/a9Q9yZxMtm94bQ==",
- "requires": {
+ "version": "0.0.97",
+ "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.97.tgz",
+ "integrity": "sha512-UO8NQrOJoAq+UQGR8TXCRzr6jX9qSnEot9FHP7zdwlKH1sGntbQpyM5BXdwfkyXo+oh1qstTGkyR4s20CY7Yrw==",
+ "requires": {
+ "@emotion/react": "^11.11.1",
+ "@emotion/styled": "^11.11.0",
+ "@mui/material": "^5.13.7",
+ "@mui/styled-engine-sc": "^5.12.0",
"color": "^4.2.3",
+ "npm": "^9.8.1",
"react-color": "^2.19.3",
"react-icons": "^4.3.1",
- "react-measure": "^2.5.2"
+ "react-measure": "^2.5.2",
+ "styled-components": "^6.0.3"
},
"dependencies": {
+ "@babel/cli": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.10.tgz",
+ "integrity": "sha512-rM9ZMmaII630zGvtMtQ3P4GyHs28CHLYE9apLG7L8TgaSqcfoIGrlLSLsh4Q8kDTdZQQEXZm1M0nQtOvU/2heg==",
+ "requires": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
+ "chokidar": "^3.4.0",
+ "commander": "^4.0.1",
+ "convert-source-map": "^1.1.0",
+ "fs-readdir-recursive": "^1.1.0",
+ "glob": "^7.2.0",
+ "make-dir": "^2.1.0",
+ "slash": "^2.0.0"
+ }
+ },
+ "@emotion/babel-plugin": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
+ "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/serialize": "^1.1.2",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "@emotion/cache": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
+ "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
+ "requires": {
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/sheet": "^1.2.2",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "stylis": "4.2.0"
+ }
+ },
+ "@emotion/hash": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
+ "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
+ },
+ "@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+ },
+ "@emotion/react": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz",
+ "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==",
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.11.0",
+ "@emotion/cache": "^11.11.0",
+ "@emotion/serialize": "^1.1.2",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "hoist-non-react-statics": "^3.3.1"
+ }
+ },
+ "@emotion/serialize": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz",
+ "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==",
+ "requires": {
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/unitless": "^0.8.1",
+ "@emotion/utils": "^1.2.1",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@emotion/sheet": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
+ "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
+ },
+ "@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
+ },
+ "@emotion/utils": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
+ "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
+ },
+ "@emotion/weak-memoize": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
+ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
+ },
+ "@mui/base": {
+ "version": "5.0.0-beta.11",
+ "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.11.tgz",
+ "integrity": "sha512-FdKZGPd8qmC3ZNke7CNhzcEgToc02M6WYZc9hcBsNQ17bgAd3s9F//1bDDYgMVBYxDM71V0sv/hBHlOY4I1ZVA==",
+ "requires": {
+ "@babel/runtime": "^7.22.6",
+ "@emotion/is-prop-valid": "^1.2.1",
+ "@mui/types": "^7.2.4",
+ "@mui/utils": "^5.14.5",
+ "@popperjs/core": "^2.11.8",
+ "clsx": "^2.0.0",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
+ "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ }
+ }
+ },
+ "@mui/core-downloads-tracker": {
+ "version": "5.14.5",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.5.tgz",
+ "integrity": "sha512-+wpGH1USwPcKMFPMvXqYPC6fEvhxM3FzxC8lyDiNK/imLyyJ6y2DPb1Oue7OGIKJWBmYBqrWWtfovrxd1aJHTA=="
+ },
+ "@mui/material": {
+ "version": "5.14.5",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.5.tgz",
+ "integrity": "sha512-4qa4GMfuZH0Ai3mttk5ccXP8a3sf7aPlAJwyMrUSz6h9hPri6BPou94zeu3rENhhmKLby9S/W1y+pmficy8JKA==",
+ "requires": {
+ "@babel/runtime": "^7.22.6",
+ "@mui/base": "5.0.0-beta.11",
+ "@mui/core-downloads-tracker": "^5.14.5",
+ "@mui/system": "^5.14.5",
+ "@mui/types": "^7.2.4",
+ "@mui/utils": "^5.14.5",
+ "@types/react-transition-group": "^4.4.6",
+ "clsx": "^2.0.0",
+ "csstype": "^3.1.2",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0",
+ "react-transition-group": "^4.4.5"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
+ "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ }
+ }
+ },
+ "@mui/private-theming": {
+ "version": "5.14.5",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.5.tgz",
+ "integrity": "sha512-cC4C5RrpXpDaaZyH9QwmPhRLgz+f2SYbOty3cPkk4qPSOSfif2ZEcDD9HTENKDDd9deB+xkPKzzZhi8cxIx8Ig==",
+ "requires": {
+ "@babel/runtime": "^7.22.6",
+ "@mui/utils": "^5.14.5",
+ "prop-types": "^15.8.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
+ "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ }
+ }
+ },
+ "@mui/styled-engine": {
+ "version": "5.13.2",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz",
+ "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "@emotion/cache": "^11.11.0",
+ "csstype": "^3.1.2",
+ "prop-types": "^15.8.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
+ "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ }
+ }
+ },
+ "@mui/system": {
+ "version": "5.14.5",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.5.tgz",
+ "integrity": "sha512-mextXZHDeGcR7E1kx43TRARrVXy+gI4wzpUgNv7MqZs1dvTVXQGVeAT6ydj9d6FUqHBPMNLGV/21vJOrpqsL+w==",
+ "requires": {
+ "@babel/runtime": "^7.22.6",
+ "@mui/private-theming": "^5.14.5",
+ "@mui/styled-engine": "^5.13.2",
+ "@mui/types": "^7.2.4",
+ "@mui/utils": "^5.14.5",
+ "clsx": "^2.0.0",
+ "csstype": "^3.1.2",
+ "prop-types": "^15.8.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
+ "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ }
+ }
+ },
+ "@mui/utils": {
+ "version": "5.14.5",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.5.tgz",
+ "integrity": "sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==",
+ "requires": {
+ "@babel/runtime": "^7.22.6",
+ "@types/prop-types": "^15.7.5",
+ "@types/react-is": "^18.2.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
+ "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ }
+ }
+ },
+ "@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
+ },
+ "@types/react-transition-group": {
+ "version": "4.4.6",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
+ "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "optional": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ }
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "optional": true
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "optional": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "optional": true,
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ }
+ },
+ "clsx": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
+ "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
+ },
"color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -4528,6 +6863,2099 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="
+ },
+ "cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "requires": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ }
+ },
+ "css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
+ },
+ "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=="
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "optional": true,
+ "requires": {
+ "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==",
+ "optional": 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==",
+ "optional": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "optional": true
+ },
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
+ },
+ "npm": {
+ "version": "9.8.1",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-9.8.1.tgz",
+ "integrity": "sha512-AfDvThQzsIXhYgk9zhbk5R+lh811lKkLAeQMMhSypf1BM7zUafeIIBzMzespeuVEJ0+LvY36oRQYf7IKLzU3rw==",
+ "requires": {
+ "@isaacs/string-locale-compare": "^1.1.0",
+ "@npmcli/arborist": "^6.3.0",
+ "@npmcli/config": "^6.2.1",
+ "@npmcli/fs": "^3.1.0",
+ "@npmcli/map-workspaces": "^3.0.4",
+ "@npmcli/package-json": "^4.0.1",
+ "@npmcli/promise-spawn": "^6.0.2",
+ "@npmcli/run-script": "^6.0.2",
+ "abbrev": "^2.0.0",
+ "archy": "~1.0.0",
+ "cacache": "^17.1.3",
+ "chalk": "^5.3.0",
+ "ci-info": "^3.8.0",
+ "cli-columns": "^4.0.0",
+ "cli-table3": "^0.6.3",
+ "columnify": "^1.6.0",
+ "fastest-levenshtein": "^1.0.16",
+ "fs-minipass": "^3.0.2",
+ "glob": "^10.2.7",
+ "graceful-fs": "^4.2.11",
+ "hosted-git-info": "^6.1.1",
+ "ini": "^4.1.1",
+ "init-package-json": "^5.0.0",
+ "is-cidr": "^4.0.2",
+ "json-parse-even-better-errors": "^3.0.0",
+ "libnpmaccess": "^7.0.2",
+ "libnpmdiff": "^5.0.19",
+ "libnpmexec": "^6.0.3",
+ "libnpmfund": "^4.0.19",
+ "libnpmhook": "^9.0.3",
+ "libnpmorg": "^5.0.4",
+ "libnpmpack": "^5.0.19",
+ "libnpmpublish": "^7.5.0",
+ "libnpmsearch": "^6.0.2",
+ "libnpmteam": "^5.0.3",
+ "libnpmversion": "^4.0.2",
+ "make-fetch-happen": "^11.1.1",
+ "minimatch": "^9.0.3",
+ "minipass": "^5.0.0",
+ "minipass-pipeline": "^1.2.4",
+ "ms": "^2.1.2",
+ "node-gyp": "^9.4.0",
+ "nopt": "^7.2.0",
+ "npm-audit-report": "^5.0.0",
+ "npm-install-checks": "^6.1.1",
+ "npm-package-arg": "^10.1.0",
+ "npm-pick-manifest": "^8.0.1",
+ "npm-profile": "^7.0.1",
+ "npm-registry-fetch": "^14.0.5",
+ "npm-user-validate": "^2.0.0",
+ "npmlog": "^7.0.1",
+ "p-map": "^4.0.0",
+ "pacote": "^15.2.0",
+ "parse-conflict-json": "^3.0.1",
+ "proc-log": "^3.0.0",
+ "qrcode-terminal": "^0.12.0",
+ "read": "^2.1.0",
+ "semver": "^7.5.4",
+ "sigstore": "^1.7.0",
+ "ssri": "^10.0.4",
+ "supports-color": "^9.4.0",
+ "tar": "^6.1.15",
+ "text-table": "~0.2.0",
+ "tiny-relative-date": "^1.3.0",
+ "treeverse": "^3.0.0",
+ "validate-npm-package-name": "^5.0.0",
+ "which": "^3.0.1",
+ "write-file-atomic": "^5.0.1"
+ },
+ "dependencies": {
+ "@colors/colors": {
+ "version": "1.5.0",
+ "bundled": true,
+ "optional": true
+ },
+ "@isaacs/cliui": {
+ "version": "8.0.2",
+ "bundled": true,
+ "requires": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "6.0.1",
+ "bundled": true
+ },
+ "emoji-regex": {
+ "version": "9.2.2",
+ "bundled": true
+ },
+ "string-width": {
+ "version": "5.1.2",
+ "bundled": true,
+ "requires": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ }
+ },
+ "string-width-cjs": {
+ "version": "npm:string-width@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"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "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=="
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "strip-ansi": {
+ "version": "7.1.0",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "^6.0.1"
+ }
+ },
+ "strip-ansi-cjs": {
+ "version": "npm:strip-ansi@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"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ }
+ }
+ },
+ "wrap-ansi-cjs": {
+ "version": "npm:wrap-ansi@7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "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=="
+ },
+ "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"
+ }
+ }
+ }
+ }
+ }
+ },
+ "@isaacs/string-locale-compare": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "@npmcli/arborist": {
+ "version": "6.3.0",
+ "bundled": true,
+ "requires": {
+ "@isaacs/string-locale-compare": "^1.1.0",
+ "@npmcli/fs": "^3.1.0",
+ "@npmcli/installed-package-contents": "^2.0.2",
+ "@npmcli/map-workspaces": "^3.0.2",
+ "@npmcli/metavuln-calculator": "^5.0.0",
+ "@npmcli/name-from-folder": "^2.0.0",
+ "@npmcli/node-gyp": "^3.0.0",
+ "@npmcli/package-json": "^4.0.0",
+ "@npmcli/query": "^3.0.0",
+ "@npmcli/run-script": "^6.0.0",
+ "bin-links": "^4.0.1",
+ "cacache": "^17.0.4",
+ "common-ancestor-path": "^1.0.1",
+ "hosted-git-info": "^6.1.1",
+ "json-parse-even-better-errors": "^3.0.0",
+ "json-stringify-nice": "^1.1.4",
+ "minimatch": "^9.0.0",
+ "nopt": "^7.0.0",
+ "npm-install-checks": "^6.0.0",
+ "npm-package-arg": "^10.1.0",
+ "npm-pick-manifest": "^8.0.1",
+ "npm-registry-fetch": "^14.0.3",
+ "npmlog": "^7.0.1",
+ "pacote": "^15.0.8",
+ "parse-conflict-json": "^3.0.0",
+ "proc-log": "^3.0.0",
+ "promise-all-reject-late": "^1.0.0",
+ "promise-call-limit": "^1.0.2",
+ "read-package-json-fast": "^3.0.2",
+ "semver": "^7.3.7",
+ "ssri": "^10.0.1",
+ "treeverse": "^3.0.0",
+ "walk-up-path": "^3.0.1"
+ }
+ },
+ "@npmcli/config": {
+ "version": "6.2.1",
+ "bundled": true,
+ "requires": {
+ "@npmcli/map-workspaces": "^3.0.2",
+ "ci-info": "^3.8.0",
+ "ini": "^4.1.0",
+ "nopt": "^7.0.0",
+ "proc-log": "^3.0.0",
+ "read-package-json-fast": "^3.0.2",
+ "semver": "^7.3.5",
+ "walk-up-path": "^3.0.1"
+ }
+ },
+ "@npmcli/disparity-colors": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "ansi-styles": "^4.3.0"
+ }
+ },
+ "@npmcli/fs": {
+ "version": "3.1.0",
+ "bundled": true,
+ "requires": {
+ "semver": "^7.3.5"
+ }
+ },
+ "@npmcli/git": {
+ "version": "4.1.0",
+ "bundled": true,
+ "requires": {
+ "@npmcli/promise-spawn": "^6.0.0",
+ "lru-cache": "^7.4.4",
+ "npm-pick-manifest": "^8.0.0",
+ "proc-log": "^3.0.0",
+ "promise-inflight": "^1.0.1",
+ "promise-retry": "^2.0.1",
+ "semver": "^7.3.5",
+ "which": "^3.0.0"
+ }
+ },
+ "@npmcli/installed-package-contents": {
+ "version": "2.0.2",
+ "bundled": true,
+ "requires": {
+ "npm-bundled": "^3.0.0",
+ "npm-normalize-package-bin": "^3.0.0"
+ }
+ },
+ "@npmcli/map-workspaces": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "@npmcli/name-from-folder": "^2.0.0",
+ "glob": "^10.2.2",
+ "minimatch": "^9.0.0",
+ "read-package-json-fast": "^3.0.0"
+ }
+ },
+ "@npmcli/metavuln-calculator": {
+ "version": "5.0.1",
+ "bundled": true,
+ "requires": {
+ "cacache": "^17.0.0",
+ "json-parse-even-better-errors": "^3.0.0",
+ "pacote": "^15.0.0",
+ "semver": "^7.3.5"
+ }
+ },
+ "@npmcli/name-from-folder": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "@npmcli/node-gyp": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "@npmcli/package-json": {
+ "version": "4.0.1",
+ "bundled": true,
+ "requires": {
+ "@npmcli/git": "^4.1.0",
+ "glob": "^10.2.2",
+ "hosted-git-info": "^6.1.1",
+ "json-parse-even-better-errors": "^3.0.0",
+ "normalize-package-data": "^5.0.0",
+ "proc-log": "^3.0.0",
+ "semver": "^7.5.3"
+ }
+ },
+ "@npmcli/promise-spawn": {
+ "version": "6.0.2",
+ "bundled": true,
+ "requires": {
+ "which": "^3.0.0"
+ }
+ },
+ "@npmcli/query": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "postcss-selector-parser": "^6.0.10"
+ }
+ },
+ "@npmcli/run-script": {
+ "version": "6.0.2",
+ "bundled": true,
+ "requires": {
+ "@npmcli/node-gyp": "^3.0.0",
+ "@npmcli/promise-spawn": "^6.0.0",
+ "node-gyp": "^9.0.0",
+ "read-package-json-fast": "^3.0.0",
+ "which": "^3.0.0"
+ }
+ },
+ "@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "bundled": true,
+ "optional": true
+ },
+ "@sigstore/protobuf-specs": {
+ "version": "0.1.0",
+ "bundled": true
+ },
+ "@sigstore/tuf": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "@sigstore/protobuf-specs": "^0.1.0",
+ "tuf-js": "^1.1.7"
+ }
+ },
+ "@tootallnate/once": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "@tufjs/canonical-json": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "@tufjs/models": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "@tufjs/canonical-json": "1.0.0",
+ "minimatch": "^9.0.0"
+ }
+ },
+ "abbrev": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "abort-controller": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "event-target-shim": "^5.0.0"
+ }
+ },
+ "agent-base": {
+ "version": "6.0.2",
+ "bundled": true,
+ "requires": {
+ "debug": "4"
+ }
+ },
+ "agentkeepalive": {
+ "version": "4.3.0",
+ "bundled": true,
+ "requires": {
+ "debug": "^4.1.0",
+ "depd": "^2.0.0",
+ "humanize-ms": "^1.2.1"
+ }
+ },
+ "aggregate-error": {
+ "version": "3.1.0",
+ "bundled": true,
+ "requires": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ }
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "bundled": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "bundled": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "aproba": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "archy": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "are-we-there-yet": {
+ "version": "4.0.0",
+ "bundled": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^4.1.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "base64-js": {
+ "version": "1.5.1",
+ "bundled": true
+ },
+ "bin-links": {
+ "version": "4.0.2",
+ "bundled": true,
+ "requires": {
+ "cmd-shim": "^6.0.0",
+ "npm-normalize-package-bin": "^3.0.0",
+ "read-cmd-shim": "^4.0.0",
+ "write-file-atomic": "^5.0.0"
+ }
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "bundled": true
+ },
+ "brace-expansion": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "buffer": {
+ "version": "6.0.3",
+ "bundled": true,
+ "requires": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "builtins": {
+ "version": "5.0.1",
+ "bundled": true,
+ "requires": {
+ "semver": "^7.0.0"
+ }
+ },
+ "cacache": {
+ "version": "17.1.3",
+ "bundled": true,
+ "requires": {
+ "@npmcli/fs": "^3.1.0",
+ "fs-minipass": "^3.0.0",
+ "glob": "^10.2.2",
+ "lru-cache": "^7.7.1",
+ "minipass": "^5.0.0",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "p-map": "^4.0.0",
+ "ssri": "^10.0.0",
+ "tar": "^6.1.11",
+ "unique-filename": "^3.0.0"
+ }
+ },
+ "chalk": {
+ "version": "5.3.0",
+ "bundled": true
+ },
+ "chownr": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "ci-info": {
+ "version": "3.8.0",
+ "bundled": true
+ },
+ "cidr-regex": {
+ "version": "3.1.1",
+ "bundled": true,
+ "requires": {
+ "ip-regex": "^4.1.0"
+ }
+ },
+ "clean-stack": {
+ "version": "2.2.0",
+ "bundled": true
+ },
+ "cli-columns": {
+ "version": "4.0.0",
+ "bundled": true,
+ "requires": {
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "cli-table3": {
+ "version": "0.6.3",
+ "bundled": true,
+ "requires": {
+ "@colors/colors": "1.5.0",
+ "string-width": "^4.2.0"
+ }
+ },
+ "clone": {
+ "version": "1.0.4",
+ "bundled": true
+ },
+ "cmd-shim": {
+ "version": "6.0.1",
+ "bundled": true
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "bundled": true
+ },
+ "color-support": {
+ "version": "1.1.3",
+ "bundled": true
+ },
+ "columnify": {
+ "version": "1.6.0",
+ "bundled": true,
+ "requires": {
+ "strip-ansi": "^6.0.1",
+ "wcwidth": "^1.0.0"
+ }
+ },
+ "common-ancestor-path": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "bundled": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "dependencies": {
+ "which": {
+ "version": "2.0.2",
+ "bundled": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "cssesc": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "debug": {
+ "version": "4.3.4",
+ "bundled": true,
+ "requires": {
+ "ms": "2.1.2"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.2",
+ "bundled": true
+ }
+ }
+ },
+ "defaults": {
+ "version": "1.0.4",
+ "bundled": true,
+ "requires": {
+ "clone": "^1.0.2"
+ }
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "depd": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "diff": {
+ "version": "5.1.0",
+ "bundled": true
+ },
+ "eastasianwidth": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "bundled": true
+ },
+ "encoding": {
+ "version": "0.1.13",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "env-paths": {
+ "version": "2.2.1",
+ "bundled": true
+ },
+ "err-code": {
+ "version": "2.0.3",
+ "bundled": true
+ },
+ "event-target-shim": {
+ "version": "5.0.1",
+ "bundled": true
+ },
+ "events": {
+ "version": "3.3.0",
+ "bundled": true
+ },
+ "exponential-backoff": {
+ "version": "3.1.1",
+ "bundled": true
+ },
+ "fastest-levenshtein": {
+ "version": "1.0.16",
+ "bundled": true
+ },
+ "foreground-child": {
+ "version": "3.1.1",
+ "bundled": true,
+ "requires": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ }
+ },
+ "fs-minipass": {
+ "version": "3.0.2",
+ "bundled": true,
+ "requires": {
+ "minipass": "^5.0.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "gauge": {
+ "version": "5.0.1",
+ "bundled": true,
+ "requires": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^4.0.1",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ }
+ },
+ "glob": {
+ "version": "10.2.7",
+ "bundled": true,
+ "requires": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.0.3",
+ "minimatch": "^9.0.1",
+ "minipass": "^5.0.0 || ^6.0.2",
+ "path-scurry": "^1.7.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.11",
+ "bundled": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "bundled": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true
+ },
+ "hosted-git-info": {
+ "version": "6.1.1",
+ "bundled": true,
+ "requires": {
+ "lru-cache": "^7.5.1"
+ }
+ },
+ "http-cache-semantics": {
+ "version": "4.1.1",
+ "bundled": true
+ },
+ "http-proxy-agent": {
+ "version": "5.0.0",
+ "bundled": true,
+ "requires": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ }
+ },
+ "https-proxy-agent": {
+ "version": "5.0.1",
+ "bundled": true,
+ "requires": {
+ "agent-base": "6",
+ "debug": "4"
+ }
+ },
+ "humanize-ms": {
+ "version": "1.2.1",
+ "bundled": true,
+ "requires": {
+ "ms": "^2.0.0"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.6.3",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ }
+ },
+ "ieee754": {
+ "version": "1.2.1",
+ "bundled": true
+ },
+ "ignore-walk": {
+ "version": "6.0.3",
+ "bundled": true,
+ "requires": {
+ "minimatch": "^9.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "bundled": true
+ },
+ "indent-string": {
+ "version": "4.0.0",
+ "bundled": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "bundled": true
+ },
+ "ini": {
+ "version": "4.1.1",
+ "bundled": true
+ },
+ "init-package-json": {
+ "version": "5.0.0",
+ "bundled": true,
+ "requires": {
+ "npm-package-arg": "^10.0.0",
+ "promzard": "^1.0.0",
+ "read": "^2.0.0",
+ "read-package-json": "^6.0.0",
+ "semver": "^7.3.5",
+ "validate-npm-package-license": "^3.0.4",
+ "validate-npm-package-name": "^5.0.0"
+ }
+ },
+ "ip": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "ip-regex": {
+ "version": "4.3.0",
+ "bundled": true
+ },
+ "is-cidr": {
+ "version": "4.0.2",
+ "bundled": true,
+ "requires": {
+ "cidr-regex": "^3.1.1"
+ }
+ },
+ "is-core-module": {
+ "version": "2.12.1",
+ "bundled": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "is-lambda": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "jackspeak": {
+ "version": "2.2.1",
+ "bundled": true,
+ "requires": {
+ "@isaacs/cliui": "^8.0.2",
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "json-parse-even-better-errors": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "json-stringify-nice": {
+ "version": "1.1.4",
+ "bundled": true
+ },
+ "jsonparse": {
+ "version": "1.3.1",
+ "bundled": true
+ },
+ "just-diff": {
+ "version": "6.0.2",
+ "bundled": true
+ },
+ "just-diff-apply": {
+ "version": "5.5.0",
+ "bundled": true
+ },
+ "libnpmaccess": {
+ "version": "7.0.2",
+ "bundled": true,
+ "requires": {
+ "npm-package-arg": "^10.1.0",
+ "npm-registry-fetch": "^14.0.3"
+ }
+ },
+ "libnpmdiff": {
+ "version": "5.0.19",
+ "bundled": true,
+ "requires": {
+ "@npmcli/arborist": "^6.3.0",
+ "@npmcli/disparity-colors": "^3.0.0",
+ "@npmcli/installed-package-contents": "^2.0.2",
+ "binary-extensions": "^2.2.0",
+ "diff": "^5.1.0",
+ "minimatch": "^9.0.0",
+ "npm-package-arg": "^10.1.0",
+ "pacote": "^15.0.8",
+ "tar": "^6.1.13"
+ }
+ },
+ "libnpmexec": {
+ "version": "6.0.3",
+ "bundled": true,
+ "requires": {
+ "@npmcli/arborist": "^6.3.0",
+ "@npmcli/run-script": "^6.0.0",
+ "ci-info": "^3.7.1",
+ "npm-package-arg": "^10.1.0",
+ "npmlog": "^7.0.1",
+ "pacote": "^15.0.8",
+ "proc-log": "^3.0.0",
+ "read": "^2.0.0",
+ "read-package-json-fast": "^3.0.2",
+ "semver": "^7.3.7",
+ "walk-up-path": "^3.0.1"
+ }
+ },
+ "libnpmfund": {
+ "version": "4.0.19",
+ "bundled": true,
+ "requires": {
+ "@npmcli/arborist": "^6.3.0"
+ }
+ },
+ "libnpmhook": {
+ "version": "9.0.3",
+ "bundled": true,
+ "requires": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^14.0.3"
+ }
+ },
+ "libnpmorg": {
+ "version": "5.0.4",
+ "bundled": true,
+ "requires": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^14.0.3"
+ }
+ },
+ "libnpmpack": {
+ "version": "5.0.19",
+ "bundled": true,
+ "requires": {
+ "@npmcli/arborist": "^6.3.0",
+ "@npmcli/run-script": "^6.0.0",
+ "npm-package-arg": "^10.1.0",
+ "pacote": "^15.0.8"
+ }
+ },
+ "libnpmpublish": {
+ "version": "7.5.0",
+ "bundled": true,
+ "requires": {
+ "ci-info": "^3.6.1",
+ "normalize-package-data": "^5.0.0",
+ "npm-package-arg": "^10.1.0",
+ "npm-registry-fetch": "^14.0.3",
+ "proc-log": "^3.0.0",
+ "semver": "^7.3.7",
+ "sigstore": "^1.4.0",
+ "ssri": "^10.0.1"
+ }
+ },
+ "libnpmsearch": {
+ "version": "6.0.2",
+ "bundled": true,
+ "requires": {
+ "npm-registry-fetch": "^14.0.3"
+ }
+ },
+ "libnpmteam": {
+ "version": "5.0.3",
+ "bundled": true,
+ "requires": {
+ "aproba": "^2.0.0",
+ "npm-registry-fetch": "^14.0.3"
+ }
+ },
+ "libnpmversion": {
+ "version": "4.0.2",
+ "bundled": true,
+ "requires": {
+ "@npmcli/git": "^4.0.1",
+ "@npmcli/run-script": "^6.0.0",
+ "json-parse-even-better-errors": "^3.0.0",
+ "proc-log": "^3.0.0",
+ "semver": "^7.3.7"
+ }
+ },
+ "lru-cache": {
+ "version": "7.18.3",
+ "bundled": true
+ },
+ "make-fetch-happen": {
+ "version": "11.1.1",
+ "bundled": true,
+ "requires": {
+ "agentkeepalive": "^4.2.1",
+ "cacache": "^17.0.0",
+ "http-cache-semantics": "^4.1.1",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "is-lambda": "^1.0.1",
+ "lru-cache": "^7.7.1",
+ "minipass": "^5.0.0",
+ "minipass-fetch": "^3.0.0",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^0.6.3",
+ "promise-retry": "^2.0.1",
+ "socks-proxy-agent": "^7.0.0",
+ "ssri": "^10.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "9.0.3",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ },
+ "minipass": {
+ "version": "5.0.0",
+ "bundled": true
+ },
+ "minipass-collect": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ },
+ "minipass-fetch": {
+ "version": "3.0.3",
+ "bundled": true,
+ "requires": {
+ "encoding": "^0.1.13",
+ "minipass": "^5.0.0",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^2.1.2"
+ }
+ },
+ "minipass-flush": {
+ "version": "1.0.5",
+ "bundled": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ },
+ "minipass-json-stream": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "jsonparse": "^1.3.1",
+ "minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ },
+ "minipass-pipeline": {
+ "version": "1.2.4",
+ "bundled": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ },
+ "minipass-sized": {
+ "version": "1.0.3",
+ "bundled": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ },
+ "minizlib": {
+ "version": "2.1.2",
+ "bundled": true,
+ "requires": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "1.0.4",
+ "bundled": true
+ },
+ "ms": {
+ "version": "2.1.3",
+ "bundled": true
+ },
+ "mute-stream": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "negotiator": {
+ "version": "0.6.3",
+ "bundled": true
+ },
+ "node-gyp": {
+ "version": "9.4.0",
+ "bundled": true,
+ "requires": {
+ "env-paths": "^2.2.0",
+ "exponential-backoff": "^3.1.1",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^11.0.3",
+ "nopt": "^6.0.0",
+ "npmlog": "^6.0.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.2",
+ "which": "^2.0.2"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true
+ },
+ "are-we-there-yet": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "gauge": {
+ "version": "4.0.4",
+ "bundled": true,
+ "requires": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^3.0.7",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ }
+ },
+ "glob": {
+ "version": "7.2.3",
+ "bundled": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "nopt": {
+ "version": "6.0.0",
+ "bundled": true,
+ "requires": {
+ "abbrev": "^1.0.0"
+ }
+ },
+ "npmlog": {
+ "version": "6.0.2",
+ "bundled": true,
+ "requires": {
+ "are-we-there-yet": "^3.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^4.0.3",
+ "set-blocking": "^2.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "3.6.2",
+ "bundled": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "signal-exit": {
+ "version": "3.0.7",
+ "bundled": true
+ },
+ "which": {
+ "version": "2.0.2",
+ "bundled": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "nopt": {
+ "version": "7.2.0",
+ "bundled": true,
+ "requires": {
+ "abbrev": "^2.0.0"
+ }
+ },
+ "normalize-package-data": {
+ "version": "5.0.0",
+ "bundled": true,
+ "requires": {
+ "hosted-git-info": "^6.0.0",
+ "is-core-module": "^2.8.1",
+ "semver": "^7.3.5",
+ "validate-npm-package-license": "^3.0.4"
+ }
+ },
+ "npm-audit-report": {
+ "version": "5.0.0",
+ "bundled": true
+ },
+ "npm-bundled": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "npm-normalize-package-bin": "^3.0.0"
+ }
+ },
+ "npm-install-checks": {
+ "version": "6.1.1",
+ "bundled": true,
+ "requires": {
+ "semver": "^7.1.1"
+ }
+ },
+ "npm-normalize-package-bin": {
+ "version": "3.0.1",
+ "bundled": true
+ },
+ "npm-package-arg": {
+ "version": "10.1.0",
+ "bundled": true,
+ "requires": {
+ "hosted-git-info": "^6.0.0",
+ "proc-log": "^3.0.0",
+ "semver": "^7.3.5",
+ "validate-npm-package-name": "^5.0.0"
+ }
+ },
+ "npm-packlist": {
+ "version": "7.0.4",
+ "bundled": true,
+ "requires": {
+ "ignore-walk": "^6.0.0"
+ }
+ },
+ "npm-pick-manifest": {
+ "version": "8.0.1",
+ "bundled": true,
+ "requires": {
+ "npm-install-checks": "^6.0.0",
+ "npm-normalize-package-bin": "^3.0.0",
+ "npm-package-arg": "^10.0.0",
+ "semver": "^7.3.5"
+ }
+ },
+ "npm-profile": {
+ "version": "7.0.1",
+ "bundled": true,
+ "requires": {
+ "npm-registry-fetch": "^14.0.0",
+ "proc-log": "^3.0.0"
+ }
+ },
+ "npm-registry-fetch": {
+ "version": "14.0.5",
+ "bundled": true,
+ "requires": {
+ "make-fetch-happen": "^11.0.0",
+ "minipass": "^5.0.0",
+ "minipass-fetch": "^3.0.0",
+ "minipass-json-stream": "^1.0.1",
+ "minizlib": "^2.1.2",
+ "npm-package-arg": "^10.0.0",
+ "proc-log": "^3.0.0"
+ }
+ },
+ "npm-user-validate": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "npmlog": {
+ "version": "7.0.1",
+ "bundled": true,
+ "requires": {
+ "are-we-there-yet": "^4.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^5.0.0",
+ "set-blocking": "^2.0.0"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "p-map": {
+ "version": "4.0.0",
+ "bundled": true,
+ "requires": {
+ "aggregate-error": "^3.0.0"
+ }
+ },
+ "pacote": {
+ "version": "15.2.0",
+ "bundled": true,
+ "requires": {
+ "@npmcli/git": "^4.0.0",
+ "@npmcli/installed-package-contents": "^2.0.1",
+ "@npmcli/promise-spawn": "^6.0.1",
+ "@npmcli/run-script": "^6.0.0",
+ "cacache": "^17.0.0",
+ "fs-minipass": "^3.0.0",
+ "minipass": "^5.0.0",
+ "npm-package-arg": "^10.0.0",
+ "npm-packlist": "^7.0.0",
+ "npm-pick-manifest": "^8.0.0",
+ "npm-registry-fetch": "^14.0.0",
+ "proc-log": "^3.0.0",
+ "promise-retry": "^2.0.1",
+ "read-package-json": "^6.0.0",
+ "read-package-json-fast": "^3.0.0",
+ "sigstore": "^1.3.0",
+ "ssri": "^10.0.0",
+ "tar": "^6.1.11"
+ }
+ },
+ "parse-conflict-json": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "json-parse-even-better-errors": "^3.0.0",
+ "just-diff": "^6.0.0",
+ "just-diff-apply": "^5.2.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "bundled": true
+ },
+ "path-scurry": {
+ "version": "1.9.2",
+ "bundled": true,
+ "requires": {
+ "lru-cache": "^9.1.1",
+ "minipass": "^5.0.0 || ^6.0.2"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "9.1.1",
+ "bundled": true
+ }
+ }
+ },
+ "postcss-selector-parser": {
+ "version": "6.0.13",
+ "bundled": true,
+ "requires": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ }
+ },
+ "proc-log": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "bundled": true
+ },
+ "promise-all-reject-late": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "promise-call-limit": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "promise-inflight": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "promise-retry": {
+ "version": "2.0.1",
+ "bundled": true,
+ "requires": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ }
+ },
+ "promzard": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "read": "^2.0.0"
+ }
+ },
+ "qrcode-terminal": {
+ "version": "0.12.0",
+ "bundled": true
+ },
+ "read": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "mute-stream": "~1.0.0"
+ }
+ },
+ "read-cmd-shim": {
+ "version": "4.0.0",
+ "bundled": true
+ },
+ "read-package-json": {
+ "version": "6.0.4",
+ "bundled": true,
+ "requires": {
+ "glob": "^10.2.2",
+ "json-parse-even-better-errors": "^3.0.0",
+ "normalize-package-data": "^5.0.0",
+ "npm-normalize-package-bin": "^3.0.0"
+ }
+ },
+ "read-package-json-fast": {
+ "version": "3.0.2",
+ "bundled": true,
+ "requires": {
+ "json-parse-even-better-errors": "^3.0.0",
+ "npm-normalize-package-bin": "^3.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "4.4.0",
+ "bundled": true,
+ "requires": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10"
+ }
+ },
+ "retry": {
+ "version": "0.12.0",
+ "bundled": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "bundled": true,
+ "requires": {
+ "glob": "^7.1.3"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "glob": {
+ "version": "7.2.3",
+ "bundled": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "bundled": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "7.5.4",
+ "bundled": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "6.0.0",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "bundled": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "signal-exit": {
+ "version": "4.0.2",
+ "bundled": true
+ },
+ "sigstore": {
+ "version": "1.7.0",
+ "bundled": true,
+ "requires": {
+ "@sigstore/protobuf-specs": "^0.1.0",
+ "@sigstore/tuf": "^1.0.1",
+ "make-fetch-happen": "^11.0.1"
+ }
+ },
+ "smart-buffer": {
+ "version": "4.2.0",
+ "bundled": true
+ },
+ "socks": {
+ "version": "2.7.1",
+ "bundled": true,
+ "requires": {
+ "ip": "^2.0.0",
+ "smart-buffer": "^4.2.0"
+ }
+ },
+ "socks-proxy-agent": {
+ "version": "7.0.0",
+ "bundled": true,
+ "requires": {
+ "agent-base": "^6.0.2",
+ "debug": "^4.3.3",
+ "socks": "^2.6.2"
+ }
+ },
+ "spdx-correct": {
+ "version": "3.2.0",
+ "bundled": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.3.0",
+ "bundled": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.13",
+ "bundled": true
+ },
+ "ssri": {
+ "version": "10.0.4",
+ "bundled": true,
+ "requires": {
+ "minipass": "^5.0.0"
+ }
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "bundled": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "string-width-cjs": {
+ "version": "npm:string-width-cjs@4.2.3",
+ "bundled": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "bundled": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-ansi-cjs": {
+ "version": "npm:strip-ansi-cjs@6.0.1",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "supports-color": {
+ "version": "9.4.0",
+ "bundled": true
+ },
+ "tar": {
+ "version": "6.1.15",
+ "bundled": true,
+ "requires": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^5.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "dependencies": {
+ "fs-minipass": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "bundled": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
+ }
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "bundled": true
+ },
+ "tiny-relative-date": {
+ "version": "1.3.0",
+ "bundled": true
+ },
+ "treeverse": {
+ "version": "3.0.0",
+ "bundled": true
+ },
+ "tuf-js": {
+ "version": "1.1.7",
+ "bundled": true,
+ "requires": {
+ "@tufjs/models": "1.0.4",
+ "debug": "^4.3.4",
+ "make-fetch-happen": "^11.1.1"
+ }
+ },
+ "unique-filename": {
+ "version": "3.0.0",
+ "bundled": true,
+ "requires": {
+ "unique-slug": "^4.0.0"
+ }
+ },
+ "unique-slug": {
+ "version": "4.0.0",
+ "bundled": true,
+ "requires": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "validate-npm-package-name": {
+ "version": "5.0.0",
+ "bundled": true,
+ "requires": {
+ "builtins": "^5.0.0"
+ }
+ },
+ "walk-up-path": {
+ "version": "3.0.1",
+ "bundled": true
+ },
+ "wcwidth": {
+ "version": "1.0.1",
+ "bundled": true,
+ "requires": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "which": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "wide-align": {
+ "version": "1.1.5",
+ "bundled": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "wrap-ansi": {
+ "version": "8.1.0",
+ "bundled": true,
+ "requires": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "6.0.1",
+ "bundled": true
+ },
+ "ansi-styles": {
+ "version": "6.2.1",
+ "bundled": true
+ },
+ "emoji-regex": {
+ "version": "9.2.2",
+ "bundled": true
+ },
+ "string-width": {
+ "version": "5.1.2",
+ "bundled": true,
+ "requires": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "7.1.0",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "^6.0.1"
+ }
+ }
+ }
+ },
+ "wrap-ansi-cjs": {
+ "version": "npm:wrap-ansi-cjs@7.0.0",
+ "bundled": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "write-file-atomic": {
+ "version": "5.0.1",
+ "bundled": true,
+ "requires": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ }
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "bundled": true
+ }
+ }
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
+ },
+ "postcss": {
+ "version": "8.4.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
+ "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==",
+ "requires": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "optional": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
+ "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
+ },
+ "styled-components": {
+ "version": "6.0.7",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.7.tgz",
+ "integrity": "sha512-xIwWuiRMYR43mskVsW9MGTRjSo7ol4bcVjT595fGUp3OLBJOlOgaiKaxsHdC4a2HqWKqKnh0CmcRbk5ogyDjTg==",
+ "requires": {
+ "@babel/cli": "^7.21.0",
+ "@babel/core": "^7.21.0",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/plugin-external-helpers": "^7.18.6",
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
+ "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
+ "@babel/preset-env": "^7.20.2",
+ "@babel/preset-react": "^7.18.6",
+ "@babel/preset-typescript": "^7.21.0",
+ "@babel/traverse": "^7.21.2",
+ "@emotion/is-prop-valid": "^1.2.1",
+ "@emotion/unitless": "^0.8.0",
+ "@types/stylis": "^4.0.2",
+ "css-to-react-native": "^3.2.0",
+ "csstype": "^3.1.2",
+ "postcss": "^8.4.23",
+ "shallowequal": "^1.1.0",
+ "stylis": "^4.3.0",
+ "tslib": "^2.5.0"
+ },
+ "dependencies": {
+ "stylis": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz",
+ "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ=="
+ }
+ }
+ },
+ "stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "optional": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
}
}
},
@@ -4548,14 +8976,14 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
},
"browserslist": {
- "version": "4.21.5",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
- "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
+ "version": "4.21.9",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
+ "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
"requires": {
- "caniuse-lite": "^1.0.30001449",
- "electron-to-chromium": "^1.4.284",
- "node-releases": "^2.0.8",
- "update-browserslist-db": "^1.0.10"
+ "caniuse-lite": "^1.0.30001503",
+ "electron-to-chromium": "^1.4.431",
+ "node-releases": "^2.0.12",
+ "update-browserslist-db": "^1.0.11"
}
},
"bson": {
@@ -4788,9 +9216,9 @@
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="
},
"caniuse-lite": {
- "version": "1.0.30001451",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz",
- "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w=="
+ "version": "1.0.30001514",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz",
+ "integrity": "sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ=="
},
"canvas": {
"version": "2.11.0",
@@ -5217,7 +9645,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",
@@ -5227,7 +9655,7 @@
"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-commons": {
"version": "2.1.1",
@@ -5602,6 +10030,14 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz",
"integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw=="
},
+ "core-js-compat": {
+ "version": "3.32.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz",
+ "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==",
+ "requires": {
+ "browserslist": "^4.21.9"
+ }
+ },
"core-js-pure": {
"version": "3.28.0",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.28.0.tgz",
@@ -5947,7 +10383,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",
@@ -7031,7 +11467,7 @@
"dynamic-dedupe": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
- "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
+ "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=",
"dev": true,
"requires": {
"xtend": "^4.0.0"
@@ -7060,9 +11496,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
- "version": "1.4.295",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.295.tgz",
- "integrity": "sha512-lEO94zqf1bDA3aepxwnWoHUjA8sZ+2owgcSZjYQy0+uOSEclJX0VieZC+r+wLpSxUHRd6gG32znTWmr+5iGzFw=="
+ "version": "1.4.454",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz",
+ "integrity": "sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ=="
},
"emoji-regex": {
"version": "7.0.3",
@@ -7161,7 +11597,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",
@@ -8723,8 +13159,7 @@
"events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
- "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
- "dev": true
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
},
"eventsource": {
"version": "2.0.2",
@@ -9291,6 +13726,14 @@
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
+ "file-selector": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+ "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -9691,7 +14134,7 @@
"fs-extra": {
"version": "0.26.7",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
- "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==",
+ "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^2.1.0",
@@ -9733,6 +14176,11 @@
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
"integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q=="
},
+ "fs-readdir-recursive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
+ "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA=="
+ },
"fs-write-stream-atomic": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@@ -9930,6 +14378,11 @@
"json-bigint": "^0.3.0"
}
},
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
+ },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -10351,14 +14804,14 @@
"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",
@@ -10934,7 +15387,7 @@
"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",
@@ -12045,7 +16498,7 @@
"jsonfile": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
- "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
+ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"requires": {
"graceful-fs": "^4.1.6"
}
@@ -12468,7 +16921,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",
@@ -12729,7 +17182,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"
},
@@ -12737,7 +17190,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=="
}
}
},
@@ -14075,9 +18528,9 @@
}
},
"node-releases": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
- "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
+ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
},
"node-sass": {
"version": "4.14.1",
@@ -14423,7 +18876,7 @@
"dependencies": {
"@iarna/cli": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@iarna/cli/-/cli-2.1.0.tgz",
+ "resolved": false,
"integrity": "sha512-rvVVqDa2g860niRbqs3D5RhL4la3dc1vwk+NlpKPZxKaMSHtE2se6C2x8NeveN+rcjp3/686X+u+09CZ+7lmAQ==",
"requires": {
"glob": "^7.1.2",
@@ -14526,7 +18979,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -14541,7 +18994,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -14550,12 +19003,12 @@
},
"asap": {
"version": "2.0.6",
- "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "resolved": false,
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
},
"asn1": {
"version": "0.2.6",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "resolved": false,
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"requires": {
"safer-buffer": "~2.1.0"
@@ -14563,7 +19016,7 @@
},
"assert-plus": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
},
"asynckit": {
@@ -14573,22 +19026,22 @@
},
"aws-sign2": {
"version": "0.7.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "resolved": false,
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
},
"aws4": {
"version": "1.11.0",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+ "resolved": false,
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"balanced-match": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "resolved": false,
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "resolved": false,
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"requires": {
"tweetnacl": "^0.14.3"
@@ -14609,7 +19062,7 @@
},
"bluebird": {
"version": "3.7.2",
- "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "resolved": false,
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"boxen": {
@@ -14657,7 +19110,7 @@
},
"cacache": {
"version": "12.0.4",
- "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+ "resolved": false,
"integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
"requires": {
"bluebird": "^3.5.5",
@@ -14694,7 +19147,7 @@
},
"caseless": {
"version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "resolved": false,
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
},
"chalk": {
@@ -14838,7 +19291,7 @@
},
"combined-stream": {
"version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "resolved": false,
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
@@ -14846,7 +19299,7 @@
},
"concat-map": {
"version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "resolved": false,
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"concat-stream": {
@@ -14876,7 +19329,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -14891,7 +19344,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -14900,7 +19353,7 @@
},
"config-chain": {
"version": "1.1.13",
- "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
+ "resolved": false,
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
"requires": {
"ini": "^1.3.4",
@@ -15001,7 +19454,7 @@
},
"dashdash": {
"version": "1.14.1",
- "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "resolved": false,
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"requires": {
"assert-plus": "^1.0.0"
@@ -15034,7 +19487,7 @@
},
"decode-uri-component": {
"version": "0.2.2",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+ "resolved": false,
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
},
"deep-extend": {
@@ -15080,7 +19533,7 @@
},
"dezalgo": {
"version": "1.0.4",
- "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "resolved": false,
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"requires": {
"asap": "^2.0.0",
@@ -15132,7 +19585,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15147,7 +19600,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15156,7 +19609,7 @@
},
"ecc-jsbn": {
"version": "0.1.2",
- "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"requires": {
"jsbn": "~0.1.0",
@@ -15191,7 +19644,7 @@
},
"env-paths": {
"version": "2.2.1",
- "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "resolved": false,
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
},
"err-code": {
@@ -15275,7 +19728,7 @@
},
"extsprintf": {
"version": "1.3.0",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "resolved": false,
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
},
"fast-json-stable-stringify": {
@@ -15285,12 +19738,12 @@
},
"figgy-pudding": {
"version": "3.5.2",
- "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+ "resolved": false,
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw=="
},
"filter-obj": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
+ "resolved": false,
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
},
"find-npm-prefix": {
@@ -15323,7 +19776,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15338,7 +19791,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15347,12 +19800,12 @@
},
"forever-agent": {
"version": "0.6.1",
- "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "resolved": false,
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
},
"form-data": {
"version": "2.3.3",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "resolved": false,
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
@@ -15385,7 +19838,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15400,7 +19853,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15468,7 +19921,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15483,7 +19936,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -15492,7 +19945,7 @@
},
"fs.realpath": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"function-bind": {
@@ -15582,7 +20035,7 @@
},
"getpass": {
"version": "0.1.7",
- "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "resolved": false,
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"requires": {
"assert-plus": "^1.0.0"
@@ -15590,7 +20043,7 @@
},
"glob": {
"version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "resolved": false,
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"requires": {
"fs.realpath": "^1.0.0",
@@ -15603,7 +20056,7 @@
"dependencies": {
"minimatch": {
"version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
@@ -15646,12 +20099,12 @@
},
"graceful-fs": {
"version": "4.2.10",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+ "resolved": false,
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
},
"har-schema": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
},
"har-validator": {
@@ -15730,7 +20183,7 @@
},
"http-signature": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "resolved": false,
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
"requires": {
"assert-plus": "^1.0.0",
@@ -15857,7 +20310,7 @@
},
"is-cidr": {
"version": "3.1.1",
- "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.1.1.tgz",
+ "resolved": false,
"integrity": "sha512-Gx+oErgq1j2jAKCR2Kbq0b3wbH0vQKqZ0wOlHxm0o56nq51Cs/DZA8oz9dMDhbHyHEGgJ86eTeVudtgMMOx3Mw==",
"requires": {
"cidr-regex": "^2.0.10"
@@ -15936,7 +20389,7 @@
},
"is-typedarray": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"isarray": {
@@ -15951,12 +20404,12 @@
},
"isstream": {
"version": "0.1.2",
- "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
},
"jsbn": {
"version": "0.1.1",
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "resolved": false,
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
},
"json-parse-better-errors": {
@@ -15966,7 +20419,7 @@
},
"json-parse-even-better-errors": {
"version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "resolved": false,
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema": {
@@ -15976,7 +20429,7 @@
},
"json-stringify-safe": {
"version": "5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "resolved": false,
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"jsonparse": {
@@ -16194,7 +20647,7 @@
},
"lock-verify": {
"version": "2.2.2",
- "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.2.2.tgz",
+ "resolved": false,
"integrity": "sha512-2CUNtr1ZSVKJHcYP8uEzafmmuyauCB5zZimj8TvQd/Lflt9kXVZs+8S+EbAzZLaVUDn8CYGmeC3DFGdYfnCzeQ==",
"requires": {
"@iarna/cli": "^2.1.0",
@@ -16323,7 +20776,7 @@
},
"meant": {
"version": "1.0.3",
- "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.3.tgz",
+ "resolved": false,
"integrity": "sha512-88ZRGcNxAq4EH38cQ4D85PM57pikCwS8Z99EWHODxN7KBY+UuPiqzRTtZzS8KTXO/ywSWbdjjJST2Hly/EQxLw=="
},
"mime-db": {
@@ -16341,7 +20794,7 @@
},
"minimatch": {
"version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
@@ -16390,7 +20843,7 @@
},
"mkdirp": {
"version": "0.5.6",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "resolved": false,
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"requires": {
"minimist": "^1.2.6"
@@ -16438,7 +20891,7 @@
},
"node-gyp": {
"version": "5.1.1",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz",
+ "resolved": false,
"integrity": "sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw==",
"requires": {
"env-paths": "^2.2.0",
@@ -16776,7 +21229,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -16791,7 +21244,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -16805,7 +21258,7 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "resolved": false,
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
},
"path-is-inside": {
@@ -16825,7 +21278,7 @@
},
"performance-now": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "resolved": false,
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
},
"pify": {
@@ -16874,7 +21327,7 @@
},
"proto-list": {
"version": "1.2.4",
- "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+ "resolved": false,
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="
},
"protoduck": {
@@ -16897,7 +21350,7 @@
},
"psl": {
"version": "1.9.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "resolved": false,
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"pump": {
@@ -16937,12 +21390,12 @@
},
"qs": {
"version": "6.5.3",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+ "resolved": false,
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
},
"query-string": {
"version": "6.14.1",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz",
+ "resolved": false,
"integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
"requires": {
"decode-uri-component": "^0.2.0",
@@ -16953,7 +21406,7 @@
},
"qw": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.2.tgz",
+ "resolved": false,
"integrity": "sha512-1PhZ/iLKwlVNq45dnerTMKFjMof49uqli7/0QsvPNbX5OJ3IZ8msa9lUpvPheVdP+IYYPrf6cOaVil7S35joVA=="
},
"rc": {
@@ -16999,7 +21452,7 @@
},
"read-package-json": {
"version": "2.1.2",
- "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==",
"requires": {
"glob": "^7.1.1",
@@ -17058,7 +21511,7 @@
},
"request": {
"version": "2.88.2",
- "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+ "resolved": false,
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"requires": {
"aws-sign2": "~0.7.0",
@@ -17128,7 +21581,7 @@
},
"safe-buffer": {
"version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "resolved": false,
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": {
@@ -17299,7 +21752,7 @@
},
"sshpk": {
"version": "1.17.0",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
+ "resolved": false,
"integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
"requires": {
"asn1": "~0.2.3",
@@ -17355,7 +21808,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -17370,7 +21823,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -17384,7 +21837,7 @@
},
"strict-uri-encode": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
},
"string-width": {
@@ -17540,7 +21993,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -17555,7 +22008,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
@@ -17574,7 +22027,7 @@
},
"tough-cookie": {
"version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "resolved": false,
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"requires": {
"psl": "^1.1.28",
@@ -17583,7 +22036,7 @@
"dependencies": {
"punycode": {
"version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "resolved": false,
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
@@ -17598,7 +22051,7 @@
},
"tweetnacl": {
"version": "0.14.5",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "resolved": false,
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
},
"typedarray": {
@@ -17669,7 +22122,7 @@
},
"uri-js": {
"version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "resolved": false,
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"requires": {
"punycode": "^2.1.0"
@@ -17710,7 +22163,7 @@
},
"uuid": {
"version": "3.4.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "resolved": false,
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"validate-npm-package-license": {
@@ -17732,7 +22185,7 @@
},
"verror": {
"version": "1.10.0",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "resolved": false,
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"requires": {
"assert-plus": "^1.0.0",
@@ -18785,9 +23238,9 @@
"dev": true
},
"popper.js": {
- "version": "1.16.1-lts",
- "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
- "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
},
"portfinder": {
"version": "1.0.32",
@@ -19757,6 +24210,16 @@
"prop-types": "^15.8.1"
}
},
+ "react-dropzone": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+ "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+ "requires": {
+ "attr-accept": "^2.2.2",
+ "file-selector": "^0.6.0",
+ "prop-types": "^15.8.1"
+ }
+ },
"react-event-listener": {
"version": "0.6.6",
"resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz",
@@ -19949,11 +24412,6 @@
"warning": "^4.0.2"
},
"dependencies": {
- "popper.js": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
- "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
- },
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
@@ -20331,11 +24789,32 @@
"@babel/runtime": "^7.9.2"
}
},
+ "regenerate": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="
+ },
+ "regenerate-unicode-properties": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz",
+ "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==",
+ "requires": {
+ "regenerate": "^1.4.2"
+ }
+ },
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
+ "regenerator-transform": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
+ "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==",
+ "requires": {
+ "@babel/runtime": "^7.8.4"
+ }
+ },
"regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -20366,6 +24845,19 @@
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
+ "regexpu-core": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz",
+ "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==",
+ "requires": {
+ "@babel/regjsgen": "^0.8.0",
+ "regenerate": "^1.4.2",
+ "regenerate-unicode-properties": "^10.1.0",
+ "regjsparser": "^0.9.1",
+ "unicode-match-property-ecmascript": "^2.0.0",
+ "unicode-match-property-value-ecmascript": "^2.1.0"
+ }
+ },
"registry-auth-token": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
@@ -20383,6 +24875,21 @@
"rc": "^1.0.1"
}
},
+ "regjsparser": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz",
+ "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==",
+ "requires": {
+ "jsesc": "~0.5.0"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA=="
+ }
+ }
+ },
"rehype-raw": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz",
@@ -20922,6 +25429,11 @@
}
}
},
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+ },
"saxes": {
"version": "3.1.11",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz",
@@ -21215,6 +25727,11 @@
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"sharp": {
"version": "0.23.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.23.4.tgz",
@@ -21741,6 +26258,11 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
},
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+ },
"source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -22224,18 +26746,13 @@
"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": "4.1.3",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz",
- "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA=="
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
+ "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q=="
},
"stylis-rule-sheet": {
"version": "0.0.10",
@@ -22543,7 +27060,7 @@
"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",
@@ -22915,7 +27432,7 @@
"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==",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
}
}
@@ -23038,6 +27555,11 @@
}
}
},
+ "tunnel": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
+ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
+ },
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -23258,6 +27780,30 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
+ "unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ=="
+ },
+ "unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "requires": {
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
+ }
+ },
+ "unicode-match-property-value-ecmascript": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz",
+ "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA=="
+ },
+ "unicode-property-aliases-ecmascript": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
+ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w=="
+ },
"unicode-trie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz",
@@ -23457,9 +28003,9 @@
"integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="
},
"update-browserslist-db": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
- "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
+ "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
"requires": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
@@ -24172,7 +28718,7 @@
"resolve-cwd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
- "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==",
+ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
"dev": true,
"requires": {
"resolve-from": "^3.0.0"
@@ -24181,7 +28727,7 @@
"resolve-from": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
- "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
"dev": true
},
"semver": {
@@ -24595,6 +29141,22 @@
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
"dev": true
},
+ "xml2js": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
+ "requires": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "dependencies": {
+ "xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
+ }
+ }
+ },
"xmlbuilder": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz",
@@ -24733,7 +29295,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": "3.1.1",
diff --git a/package.json b/package.json
index 6cd271c96..ea4e6a5e6 100644
--- a/package.json
+++ b/package.json
@@ -10,10 +10,11 @@
"child_process": "empty"
},
"scripts": {
- "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
+ "start-release": "cross-env RELEASE=true USE_AZURE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
+ "start-release-debug": "cross-env RELEASE=true USE_AZURE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --inspect -- src/server/index.ts",
"start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
"oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
- "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
+ "debug": "cross-env USE_AZURE=false NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
"monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
"test": "mocha -r ts-node/register test/**/*.ts",
@@ -130,6 +131,7 @@
"webpack-hot-middleware": "^2.25.1"
},
"dependencies": {
+ "@azure/storage-blob": "^12.14.0",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@ffmpeg/core": "0.10.0",
@@ -162,7 +164,6 @@
"@types/three": "^0.126.2",
"@types/web": "0.0.53",
"@webscopeio/react-textarea-autocomplete": "^4.9.1",
- "D": "^1.0.0",
"adm-zip": "^0.4.16",
"archiver": "^3.1.1",
"array-batcher": "^1.2.3",
@@ -177,7 +178,7 @@
"body-parser": "^1.19.2",
"bootstrap": "^4.6.1",
"brotli": "^1.3.3",
- "browndash-components": "^0.0.28",
+ "browndash-components": "^0.0.97",
"browser-assert": "^1.2.1",
"bson": "^4.6.1",
"canvas": "^2.9.3",
@@ -195,6 +196,7 @@
"cors": "^2.8.5",
"csv-parser": "^3.0.0",
"csv-stringify": "^6.3.0",
+ "D": "^1.0.0",
"d3": "^7.6.1",
"depcheck": "^0.9.2",
"equation-editor-react": "github:bobzel/equation-editor-react#useLocally",
@@ -288,6 +290,7 @@
"react-compound-slider": "^2.5.0",
"react-datepicker": "^3.8.0",
"react-dom": "^18.2.0",
+ "react-dropzone": "^14.2.3",
"react-grid-layout": "^1.3.4",
"react-icons": "^4.3.1",
"react-jsx-parser": "^1.29.0",
diff --git a/report.20230313.165455.65490.0.001.json b/report.20230313.165455.65490.0.001.json
new file mode 100644
index 000000000..689fcf9eb
--- /dev/null
+++ b/report.20230313.165455.65490.0.001.json
@@ -0,0 +1,1294 @@
+
+{
+ "header": {
+ "reportVersion": 1,
+ "event": "Allocation failed - JavaScript heap out of memory",
+ "trigger": "FatalError",
+ "filename": "report.20230313.165455.65490.0.001.json",
+ "dumpEventTime": "2023-03-13T16:54:55Z",
+ "dumpEventTimeStamp": "1678740895169",
+ "processId": 65490,
+ "cwd": "/Users/sarah/Desktop/dash/Dash-Web",
+ "commandLine": [
+ "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
+ "--max-old-space-size=2048",
+ "/Users/sarah/Desktop/dash/Dash-Web/node_modules/ts-node-dev/lib/wrap.js",
+ "/Users/sarah/Desktop/dash/Dash-Web/node_modules/fork-ts-checker-webpack-plugin/lib/service.js"
+ ],
+ "nodejsVersion": "v12.16.0",
+ "wordSize": 64,
+ "arch": "x64",
+ "platform": "darwin",
+ "componentVersions": {
+ "node": "12.16.0",
+ "v8": "7.8.279.23-node.31",
+ "uv": "1.34.0",
+ "zlib": "1.2.11",
+ "brotli": "1.0.7",
+ "ares": "1.15.0",
+ "modules": "72",
+ "nghttp2": "1.40.0",
+ "napi": "5",
+ "llhttp": "2.0.4",
+ "http_parser": "2.9.3",
+ "openssl": "1.1.1d",
+ "cldr": "35.1",
+ "icu": "64.2",
+ "tz": "2019c",
+ "unicode": "12.1"
+ },
+ "release": {
+ "name": "node",
+ "lts": "Erbium",
+ "headersUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0-headers.tar.gz",
+ "sourceUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0.tar.gz"
+ },
+ "osName": "Darwin",
+ "osRelease": "20.4.0",
+ "osVersion": "Darwin Kernel Version 20.4.0: Fri Mar 5 01:14:14 PST 2021; root:xnu-7195.101.1~3/RELEASE_X86_64",
+ "osMachine": "x86_64",
+ "cpus": [
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 95972310,
+ "nice": 0,
+ "sys": 69742110,
+ "idle": 1132989220,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1235990,
+ "nice": 0,
+ "sys": 1442360,
+ "idle": 1293767630,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 77301330,
+ "nice": 0,
+ "sys": 43426390,
+ "idle": 1175757970,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1270620,
+ "nice": 0,
+ "sys": 1351190,
+ "idle": 1293823030,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 56966210,
+ "nice": 0,
+ "sys": 28908780,
+ "idle": 1210608910,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1291530,
+ "nice": 0,
+ "sys": 1265830,
+ "idle": 1293886140,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 46074390,
+ "nice": 0,
+ "sys": 22963990,
+ "idle": 1227443730,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1311400,
+ "nice": 0,
+ "sys": 1200930,
+ "idle": 1293929770,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 36059780,
+ "nice": 0,
+ "sys": 17309680,
+ "idle": 1243110810,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1303400,
+ "nice": 0,
+ "sys": 1134770,
+ "idle": 1294002540,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 28665300,
+ "nice": 0,
+ "sys": 12769180,
+ "idle": 1255043860,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1287310,
+ "nice": 0,
+ "sys": 1073720,
+ "idle": 1294078280,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 22112060,
+ "nice": 0,
+ "sys": 9084160,
+ "idle": 1265280170,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1256320,
+ "nice": 0,
+ "sys": 1011250,
+ "idle": 1294170360,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 17120610,
+ "nice": 0,
+ "sys": 6395650,
+ "idle": 1272958030,
+ "irq": 0
+ },
+ {
+ "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
+ "speed": 2300,
+ "user": 1227540,
+ "nice": 0,
+ "sys": 963060,
+ "idle": 1294245890,
+ "irq": 0
+ }
+ ],
+ "networkInterfaces": [
+ {
+ "name": "lo0",
+ "internal": true,
+ "mac": "00:00:00:00:00:00",
+ "address": "127.0.0.1",
+ "netmask": "255.0.0.0",
+ "family": "IPv4"
+ },
+ {
+ "name": "lo0",
+ "internal": true,
+ "mac": "00:00:00:00:00:00",
+ "address": "::1",
+ "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "lo0",
+ "internal": true,
+ "mac": "00:00:00:00:00:00",
+ "address": "fe80::1",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 1
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fe80::cc1:cf3b:afa2:144f",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 6
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "2620:6e:6000:3100:148e:201a:1a33:145d",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "2620:6e:6000:3100:31e4:88bf:1195:6926",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fdac:89a:4f49:41ac:83d:26cd:abc5:e973",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fdac:89a:4f49:41ac:15b2:4a9e:88a9:34c3",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "10.38.53.246",
+ "netmask": "255.255.192.0",
+ "family": "IPv4"
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fd8c:23f:4de7:4523:cc7:18bd:a001:a86b",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fd8c:23f:4de7:4523:6cbc:dfb0:da3f:fa06",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fdc9:5cae:17e4:4c54:c1e:4892:ccc4:93ae",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fdc9:5cae:17e4:4c54:307f:315d:52f7:511f",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fdd3:9b36:2480:4c6f:cff:ec25:4ee6:54c",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "en0",
+ "internal": false,
+ "mac": "88:66:5a:29:28:77",
+ "address": "fdd3:9b36:2480:4c6f:9dba:7458:3932:be33",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 0
+ },
+ {
+ "name": "awdl0",
+ "internal": false,
+ "mac": "a6:20:c0:51:e8:8f",
+ "address": "fe80::a420:c0ff:fe51:e88f",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 12
+ },
+ {
+ "name": "llw0",
+ "internal": false,
+ "mac": "a6:20:c0:51:e8:8f",
+ "address": "fe80::a420:c0ff:fe51:e88f",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 13
+ },
+ {
+ "name": "utun0",
+ "internal": false,
+ "mac": "00:00:00:00:00:00",
+ "address": "fe80::15ac:b094:e48b:b227",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 14
+ },
+ {
+ "name": "utun1",
+ "internal": false,
+ "mac": "00:00:00:00:00:00",
+ "address": "fe80::97ec:93db:83a3:75ce",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 15
+ },
+ {
+ "name": "en5",
+ "internal": false,
+ "mac": "ac:de:48:00:11:22",
+ "address": "fe80::aede:48ff:fe00:1122",
+ "netmask": "ffff:ffff:ffff:ffff::",
+ "family": "IPv6",
+ "scopeid": 5
+ }
+ ],
+ "host": "sarahs-mbp.devices.brown.edu"
+ },
+ "javascriptStack": {
+ "message": "No stack.",
+ "stack": [
+ "Unavailable."
+ ]
+ },
+ "nativeStack": [
+ {
+ "pc": "0x000000010015c8ca",
+ "symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, v8::Local<v8::String>) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100080f3e",
+ "symbol": "node::OnFatalError(char const*, char const*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100185467",
+ "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100185403",
+ "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x000000010030b5f5",
+ "symbol": "v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x000000010030ccc4",
+ "symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100309b37",
+ "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100307afd",
+ "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x00000001003132ba",
+ "symbol": "v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100313341",
+ "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x00000001002e065b",
+ "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100618a18",
+ "symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ },
+ {
+ "pc": "0x0000000100950c19",
+ "symbol": "Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
+ }
+ ],
+ "javascriptHeap": {
+ "totalMemory": 2151882752,
+ "totalCommittedMemory": 2149824464,
+ "usedMemory": 2139828064,
+ "availableMemory": 48165544,
+ "memoryLimit": 2197815296,
+ "heapSpaces": {
+ "read_only_space": {
+ "memorySize": 262144,
+ "committedMemory": 33088,
+ "capacity": 32808,
+ "used": 32808,
+ "available": 0
+ },
+ "new_space": {
+ "memorySize": 2097152,
+ "committedMemory": 1076448,
+ "capacity": 1047456,
+ "used": 28792,
+ "available": 1018664
+ },
+ "old_space": {
+ "memorySize": 1958100992,
+ "committedMemory": 1957877576,
+ "capacity": 1951020152,
+ "used": 1950853272,
+ "available": 166880
+ },
+ "code_space": {
+ "memorySize": 15372288,
+ "committedMemory": 14957376,
+ "capacity": 13767968,
+ "used": 13767968,
+ "available": 0
+ },
+ "map_space": {
+ "memorySize": 1576960,
+ "committedMemory": 1406760,
+ "capacity": 1236000,
+ "used": 1236000,
+ "available": 0
+ },
+ "large_object_space": {
+ "memorySize": 174424064,
+ "committedMemory": 174424064,
+ "capacity": 173906440,
+ "used": 173906440,
+ "available": 0
+ },
+ "code_large_object_space": {
+ "memorySize": 49152,
+ "committedMemory": 49152,
+ "capacity": 2784,
+ "used": 2784,
+ "available": 0
+ },
+ "new_large_object_space": {
+ "memorySize": 0,
+ "committedMemory": 0,
+ "capacity": 1047456,
+ "used": 0,
+ "available": 1047456
+ }
+ }
+ },
+ "resourceUsage": {
+ "userCpuSeconds": 302.098,
+ "kernelCpuSeconds": 7.74276,
+ "cpuConsumptionPercent": 10.3834,
+ "maxRss": 2286298333184,
+ "pageFaults": {
+ "IORequired": 35,
+ "IONotRequired": 2289110
+ },
+ "fsActivity": {
+ "reads": 0,
+ "writes": 0
+ }
+ },
+ "libuv": [
+ ],
+ "environmentVariables": {
+ "npm_config_save_dev": "",
+ "npm_config_legacy_bundling": "",
+ "npm_config_dry_run": "",
+ "npm_package_dependencies_translate_google_api": "^1.0.4",
+ "npm_package_dependencies_request": "^2.88.2",
+ "npm_package_dependencies_express_flash": "0.0.2",
+ "npm_package_dependencies__fortawesome_fontawesome_svg_core": "^1.3.0",
+ "NVM_INC": "/Users/sarah/.nvm/versions/node/v12.16.0/include/node",
+ "npm_config_viewer": "man",
+ "npm_config_only": "",
+ "npm_config_commit_hooks": "true",
+ "npm_config_browser": "",
+ "npm_package_gitHead": "4c2584baf8bae0cde714c832b0768d3c08864422",
+ "npm_package_dependencies_webpack_dev_middleware": "^5.3.1",
+ "npm_package_dependencies_webpack_cli": "^4.10.0",
+ "npm_package_devDependencies_prettier": "^2.7.1",
+ "npm_package_devDependencies_awesome_typescript_loader": "^5.2.1",
+ "npm_package_devDependencies__types_archiver": "^3.1.1",
+ "npm_config_also": "",
+ "npm_package_dependencies_react_jsx_parser": "^1.29.0",
+ "npm_package_dependencies_mongoose": "^5.13.14",
+ "npm_package_dependencies_connect_flash": "^0.1.1",
+ "npm_package_browser_child_process": "false",
+ "npm_config_sign_git_commit": "",
+ "npm_config_rollback": "true",
+ "npm_package_dependencies_material_ui": "^0.20.2",
+ "npm_package_devDependencies__types_sharp": "^0.23.1",
+ "npm_package_devDependencies__types_passport_local": "^1.0.34",
+ "npm_package_devDependencies__types_dotenv": "^6.1.1",
+ "npm_package_devDependencies__types_cookie_parser": "^1.4.2",
+ "TERM_PROGRAM": "Apple_Terminal",
+ "NODE": "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
+ "npm_config_usage": "",
+ "npm_config_audit": "true",
+ "npm_package_dependencies_reveal_js": "^4.3.0",
+ "npm_package_dependencies_process": "^0.11.10",
+ "npm_package_dependencies_pdfjs": "^2.4.7",
+ "npm_package_dependencies_html_to_image": "^0.1.3",
+ "npm_package_devDependencies_file_loader": "^3.0.1",
+ "npm_package_devDependencies__types_express_flash": "0.0.0",
+ "npm_package_scripts_monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
+ "INIT_CWD": "/Users/sarah/Desktop/dash/Dash-Web",
+ "npm_package_dependencies_rehype_raw": "^6.1.1",
+ "npm_package_dependencies_react_audio_waveform": "0.0.5",
+ "npm_package_dependencies_path_browserify": "^1.0.1",
+ "npm_package_dependencies_nodemailer": "^5.1.1",
+ "npm_package_dependencies_axios": "^0.19.2",
+ "npm_package_devDependencies_typescript": "^4.7.4",
+ "NVM_CD_FLAGS": "-q",
+ "npm_config_globalignorefile": "/Users/sarah/.nvm/versions/node/v12.16.0/etc/npmignore",
+ "npm_package_dependencies_react_grid_layout": "^1.3.4",
+ "npm_package_dependencies_prosemirror_find_replace": "^0.9.0",
+ "npm_package_dependencies_normalize_css": "^8.0.1",
+ "npm_package_devDependencies_mocha": "^5.2.0",
+ "npm_package_devDependencies__types_express_session": "^1.17.5",
+ "SHELL": "/bin/zsh",
+ "TERM": "xterm-256color",
+ "npm_config_shell": "/bin/zsh",
+ "npm_config_maxsockets": "50",
+ "npm_config_init_author_url": "",
+ "npm_package_dependencies_prosemirror_dev_tools": "^3.1.0",
+ "npm_package_dependencies_p_limit": "^2.2.0",
+ "npm_package_dependencies_bson": "^4.6.1",
+ "npm_package_dependencies__types_dom_speech_recognition": "0.0.1",
+ "npm_package_devDependencies_style_loader": "^0.23.1",
+ "npm_package_devDependencies__types_react_datepicker": "^3.1.8",
+ "npm_config_shrinkwrap": "true",
+ "npm_config_parseable": "",
+ "npm_config_metrics_registry": "https://registry.npmjs.org/",
+ "npm_package_dependencies_xregexp": "^4.4.1",
+ "npm_package_dependencies_shelljs": "^0.8.5",
+ "npm_package_dependencies_bezier_curve": "^1.0.0",
+ "npm_package_devDependencies_tslint": "^5.20.1",
+ "npm_package_devDependencies__types_react_transition_group": "^4.4.5",
+ "npm_package_scripts_tsc": "tsc",
+ "TMPDIR": "/var/folders/yk/p_39q8jn673c5p8_66mcxm7r0000gn/T/",
+ "npm_config_timing": "",
+ "npm_config_init_license": "ISC",
+ "npm_package_dependencies_socket_io": "^2.5.0",
+ "npm_package_dependencies_probe_image_size": "^4.0.0",
+ "npm_package_dependencies_canvas": "^2.9.3",
+ "npm_package_dependencies__hig_theme_data": "^2.23.1",
+ "npm_package_devDependencies__types_react_select": "^3.1.2",
+ "npm_package_devDependencies__types_prosemirror_model": "^1.16.1",
+ "CONDA_SHLVL": "1",
+ "npm_config_if_present": "",
+ "npm_package_dependencies_typescript_collections": "^1.3.3",
+ "npm_package_dependencies_rimraf": "^3.0.0",
+ "npm_package_dependencies_react_autosuggest": "^9.4.3",
+ "npm_package_dependencies_flexlayout_react": "^0.3.11",
+ "npm_package_dependencies_find_in_files": "^0.5.0",
+ "npm_package_devDependencies__types_chai": "^4.3.0",
+ "CONDA_PROMPT_MODIFIER": "(base) ",
+ "TERM_PROGRAM_VERSION": "440",
+ "npm_package_dependencies_prosemirror_inputrules": "^1.1.3",
+ "npm_package_dependencies_bcrypt_nodejs": "0.0.3",
+ "npm_package_dependencies_async": "^2.6.2",
+ "npm_config_sign_git_tag": "",
+ "npm_config_init_author_email": "",
+ "npm_config_cache_max": "Infinity",
+ "npm_package_dependencies_uuid": "^3.4.0",
+ "npm_package_dependencies_supercluster": "^7.1.4",
+ "npm_package_dependencies_remark_gfm": "^3.0.1",
+ "npm_package_dependencies_connect_mongo": "^2.0.3",
+ "npm_package_dependencies_browser_assert": "^1.2.1",
+ "npm_package_devDependencies_sass_loader": "^7.3.1",
+ "npm_config_preid": "",
+ "npm_config_long": "",
+ "npm_config_local_address": "",
+ "npm_config_git_tag_version": "true",
+ "npm_config_cert": "",
+ "npm_package_dependencies_js_datepicker": "^4.6.6",
+ "npm_package_devDependencies__types_webpack_hot_middleware": "^2.25.6",
+ "npm_package_devDependencies__types_mongodb": "^3.6.20",
+ "npm_package_devDependencies__types_mocha": "^5.2.6",
+ "TERM_SESSION_ID": "BF3A3D73-8B2D-4041-BAFA-CCC983EE3D05",
+ "npm_config_registry": "https://registry.npmjs.org/",
+ "npm_config_noproxy": "",
+ "npm_config_fetch_retries": "2",
+ "npm_package_dependencies_react_compound_slider": "^2.5.0",
+ "npm_package_dependencies_prosemirror_history": "^1.2.0",
+ "npm_package_devDependencies__types_react_color": "^2.17.6",
+ "npm_package_devDependencies__types_google_maps_react": "^2.0.5",
+ "npm_package_devDependencies__types_color": "^3.0.3",
+ "npm_package_dependencies_react_dom": "^18.2.0",
+ "npm_package_dependencies_passport_local": "^1.0.0",
+ "npm_package_dependencies__octokit_core": "^4.0.4",
+ "npm_package_devDependencies__types_async": "^2.4.1",
+ "npm_package_scripts_debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
+ "npm_package_scripts_oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
+ "npm_config_versions": "",
+ "npm_config_message": "%s",
+ "npm_config_key": "",
+ "npm_package_readmeFilename": "README.md",
+ "npm_package_dependencies_react_refresh_typescript": "^2.0.7",
+ "npm_package_dependencies_image_size": "^0.7.5",
+ "npm_package_dependencies_html_to_text": "^5.1.1",
+ "npm_package_dependencies_express_validator": "^5.3.1",
+ "npm_package_devDependencies_eslint_plugin_jsx_a11y": "^6.6.0",
+ "npm_package_node_child_process": "empty",
+ "npm_package_dependencies_react_resizable_rotatable_draggable": "^0.2.0",
+ "npm_package_dependencies_got": "^12.0.1",
+ "npm_package_dependencies__types_d3_color": "^2.0.3",
+ "npm_package_devDependencies_webpack": "^5.69.1",
+ "npm_package_devDependencies__types_nodemailer": "^4.6.6",
+ "npm_package_description": "Install Node.js, then, from the project directory, run",
+ "NVM_DIR": "/Users/sarah/.nvm",
+ "USER": "sarah",
+ "npm_package_dependencies__types_d3_scale": "^3.3.2",
+ "npm_package_devDependencies_dotenv": "^8.6.0",
+ "npm_package_devDependencies__types_react": "^18.0.15",
+ "npm_package_devDependencies__types_prosemirror_transform": "^1.1.5",
+ "npm_package_devDependencies__types_prosemirror_history": "^1.0.3",
+ "npm_package_dependencies_readline": "^1.3.0",
+ "npm_package_dependencies__types_supercluster": "^7.1.0",
+ "npm_config_globalconfig": "/Users/sarah/.nvm/versions/node/v12.16.0/etc/npmrc",
+ "npm_package_dependencies_depcheck": "^0.9.2",
+ "npm_package_dependencies__types_web": "0.0.53",
+ "CONDA_EXE": "/Users/sarah/miniconda3/bin/conda",
+ "npm_config_prefer_online": "",
+ "npm_config_logs_max": "10",
+ "npm_config_always_auth": "",
+ "npm_package_dependencies_react_icons": "^4.3.1",
+ "npm_package_dependencies_passport_google_oauth20": "^2.0.0",
+ "npm_package_devDependencies_webpack_dev_server": "^3.11.3",
+ "npm_package_dependencies_url_loader": "^1.1.2",
+ "npm_package_dependencies_stream_browserify": "^3.0.0",
+ "npm_package_dependencies_prosemirror_transform": "^1.3.4",
+ "npm_package_dependencies_lodash": "^4.17.21",
+ "npm_package_dependencies_i": "^0.3.7",
+ "npm_package_devDependencies_tslint_loader": "^3.6.0",
+ "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.TKuATvqs9j/Listeners",
+ "npm_package_dependencies_words_to_numbers": "^1.5.1",
+ "npm_package_dependencies_valid_url": "^1.0.9",
+ "npm_package_dependencies_styled_components": "^4.4.1",
+ "npm_package_dependencies_class_transformer": "^0.2.0",
+ "npm_package_devDependencies_eslint": "^8.18.0",
+ "npm_package_devDependencies__types_prosemirror_inputrules": "^1.0.4",
+ "npm_package_devDependencies__types_express": "^4.17.13",
+ "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0",
+ "npm_execpath": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/bin/npm-cli.js",
+ "npm_config_global_style": "",
+ "npm_config_cache_lock_retries": "10",
+ "npm_package_dependencies_wikijs": "^6.3.3",
+ "npm_package_dependencies_bluebird": "^3.7.2",
+ "npm_config_update_notifier": "true",
+ "npm_config_cafile": "",
+ "npm_package_dependencies_util": "^0.12.4",
+ "npm_package_dependencies_raw_loader": "^1.0.0",
+ "npm_package_dependencies_https_browserify": "^1.0.0",
+ "npm_package_dependencies__fortawesome_react_fontawesome": "^0.1.19",
+ "npm_package_devDependencies__types_passport_google_oauth20": "^2.0.11",
+ "npm_package_dependencies_cors": "^2.8.5",
+ "npm_package_dependencies_bezier_js": "^4.1.1",
+ "npm_package_dependencies__fortawesome_free_brands_svg_icons": "^5.15.4",
+ "npm_config_heading": "npm",
+ "npm_config_audit_level": "low",
+ "npm_package_dependencies_chrome": "^0.1.0",
+ "npm_package_dependencies__react_three_fiber": "^6.2.3",
+ "npm_package_devDependencies_eslint_plugin_prettier": "^4.2.1",
+ "npm_package_devDependencies_copy_webpack_plugin": "^4.6.0",
+ "npm_package_devDependencies__types_react_measure": "^2.0.8",
+ "npm_package_devDependencies__types_react_dom": "^18.0.6",
+ "npm_package_devDependencies__types_mobile_detect": "^1.3.4",
+ "_CE_CONDA": "",
+ "npm_config_searchlimit": "20",
+ "npm_config_read_only": "",
+ "npm_config_offline": "",
+ "npm_config_fetch_retry_mintimeout": "10000",
+ "npm_package_dependencies_mobx_react_devtools": "^6.1.1",
+ "npm_package_dependencies_md5_file": "^5.0.0",
+ "npm_package_dependencies_forever_agent": "^0.6.1",
+ "npm_package_devDependencies__types_xregexp": "^4.4.0",
+ "npm_package_devDependencies__types_typescript": "^2.0.0",
+ "npm_package_devDependencies__types_request": "^2.48.8",
+ "npm_package_devDependencies__types_prosemirror_commands": "^1.0.4",
+ "npm_config_json": "",
+ "npm_config_access": "",
+ "npm_config_argv": "{\"remain\":[],\"cooked\":[\"start\"],\"original\":[\"start\"]}",
+ "npm_package_dependencies__fortawesome_free_solid_svg_icons": "^5.15.4",
+ "npm_package_devDependencies__types_socket_io": "^2.1.13",
+ "PATH": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/sarah/Desktop/dash/Dash-Web/node_modules/.bin:/Users/sarah/.nvm/versions/node/v12.16.0/bin:/Users/sarah/miniconda3/bin:/Users/sarah/miniconda3/condabin:/Users/sarah/.elan/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
+ "npm_config_allow_same_version": "",
+ "npm_package_dependencies_webrtc_adapter": "^7.7.1",
+ "npm_package_dependencies_react_reveal": "^1.2.2",
+ "npm_package_dependencies_prosemirror_schema_list": "^1.1.6",
+ "npm_package_dependencies__material_ui_core": "^4.12.3",
+ "npm_package_devDependencies__types_rimraf": "^2.0.5",
+ "npm_package_devDependencies__types_connect_flash": "0.0.34",
+ "npm_config_https_proxy": "",
+ "npm_config_engine_strict": "",
+ "npm_config_description": "true",
+ "npm_package_dependencies_pug": "^2.0.4",
+ "npm_package_dependencies_prosemirror_keymap": "^1.1.5",
+ "npm_package_dependencies_pdfjs_dist": "^2.14.305",
+ "npm_package_dependencies_mobile_detect": "^1.4.5",
+ "npm_package_dependencies_image_size_stream": "^1.1.0",
+ "npm_package_dependencies_golden_layout": "^1.5.9",
+ "npm_package_dependencies_child_process": "^1.0.2",
+ "npm_package_dependencies__types_d3_axis": "^2.1.3",
+ "_": "/Users/sarah/Desktop/dash/Dash-Web/node_modules/.bin/cross-env",
+ "npm_config_userconfig": "/Users/sarah/.npmrc",
+ "npm_config_init_module": "/Users/sarah/.npm-init.js",
+ "npm_package_dependencies__react_google_maps_api": "^2.7.0",
+ "CONDA_PREFIX": "/Users/sarah/miniconda3",
+ "__CFBundleIdentifier": "com.apple.Terminal",
+ "npm_config_cidr": "",
+ "npm_package_dependencies_puppeteer": "^3.3.0",
+ "npm_package_dependencies_prosemirror_view": "^1.26.5",
+ "npm_package_dependencies_mongodb": "^3.7.3",
+ "npm_package_dependencies_google_auth_library": "^4.2.4",
+ "npm_package_dependencies_bootstrap": "^4.6.1",
+ "npm_package_devDependencies_eslint_config_airbnb": "^19.0.4",
+ "PWD": "/Users/sarah/desktop/dash/dash-web",
+ "npm_config_user": "501",
+ "npm_config_node_version": "12.16.0",
+ "npm_package_dependencies_node_sass": "^4.14.1",
+ "npm_package_dependencies_howler": "^2.2.3",
+ "npm_package_dependencies_expressjs": "^1.0.1",
+ "npm_package_dependencies_core_js": "^3.28.0",
+ "npm_package_dependencies_browndash_components": "0.0.22",
+ "npm_package_devDependencies_eslint_plugin_react_hooks": "^4.6.0",
+ "npm_package_devDependencies__types_lodash": "^4.14.179",
+ "JAVA_HOME": "/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home",
+ "npm_lifecycle_event": "start",
+ "npm_package_dependencies_react_table": "^6.11.5",
+ "npm_package_dependencies_react_loading": "^2.0.3",
+ "npm_package_dependencies_mobx": "^5.15.7",
+ "npm_package_dependencies_babel": "^6.23.0",
+ "npm_package_devDependencies_jsdom": "^15.2.1",
+ "npm_package_devDependencies_chai": "^4.3.6",
+ "npm_config_save": "true",
+ "npm_config_ignore_prepublish": "",
+ "npm_config_editor": "vi",
+ "npm_config_auth_type": "legacy",
+ "npm_package_dependencies_npm": "^6.14.18",
+ "npm_package_dependencies_node_stream_zip": "^1.15.0",
+ "npm_package_dependencies_image_data_uri": "^2.0.1",
+ "npm_package_scripts_start_release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
+ "npm_package_name": "dash",
+ "LANG": "en_US.UTF-8",
+ "npm_config_tag": "latest",
+ "npm_config_script_shell": "",
+ "npm_package_dependencies_query_string": "^6.14.1",
+ "npm_package_dependencies_mobx_utils": "^5.6.2",
+ "npm_package_dependencies_file_saver": "^2.0.5",
+ "npm_package_dependencies_body_parser": "^1.19.2",
+ "npm_package_dependencies__types_reveal": "^3.3.33",
+ "npm_package_devDependencies_eslint_plugin_import": "^2.26.0",
+ "npm_package_devDependencies__types_prosemirror_view": "^1.23.1",
+ "npm_config_progress": "true",
+ "npm_config_global": "",
+ "npm_config_before": "",
+ "npm_package_dependencies_xoauth2": "^1.2.0",
+ "npm_package_dependencies_standard_http_error": "^2.0.1",
+ "npm_package_dependencies_http_browserify": "^1.7.0",
+ "npm_package_dependencies__types_d3_selection": "^2.0.1",
+ "npm_package_dependencies__hig_flyout": "^1.3.1",
+ "npm_package_devDependencies_fork_ts_checker_webpack_plugin": "^1.6.0",
+ "npm_package_scripts_build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
+ "npm_package_scripts_start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
+ "npm_config_searchstaleness": "900",
+ "npm_config_optional": "true",
+ "npm_config_ham_it_up": "",
+ "npm_package_dependencies_sharp": "^0.23.4",
+ "npm_package_dependencies_rc_switch": "^1.9.2",
+ "npm_package_dependencies_googlephotos": "^0.2.5",
+ "npm_package_dependencies_exifr": "^7.1.3",
+ "npm_package_dependencies__types_google_maps": "^3.2.3",
+ "npm_package_dependencies__types_bezier_js": "^4.1.0",
+ "npm_package_dependencies__ffmpeg_core": "0.10.0",
+ "npm_package_devDependencies_ts_loader": "^5.3.3",
+ "npm_package_devDependencies__types_bcrypt_nodejs": "0.0.30",
+ "XPC_FLAGS": "0x0",
+ "npm_config_save_prod": "",
+ "npm_config_force": "",
+ "npm_config_bin_links": "true",
+ "npm_package_devDependencies__types_youtube": "0.0.39",
+ "npm_config_searchopts": "",
+ "npm_package_dependencies_react_beautiful_dnd": "^13.1.0",
+ "npm_package_dependencies_jszip": "^3.7.1",
+ "npm_package_devDependencies__types_react_icons": "^3.0.0",
+ "npm_config_node_gyp": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
+ "npm_config_depth": "Infinity",
+ "npm_package_dependencies_google_maps_react": "^2.0.6",
+ "npm_package_dependencies_express_session": "^1.17.2",
+ "npm_package_devDependencies_eslint_plugin_node": "^11.1.0",
+ "npm_package_devDependencies_eslint_config_prettier": "^8.5.0",
+ "npm_package_main": "index.js",
+ "npm_config_sso_poll_frequency": "500",
+ "npm_config_rebuild_bundle": "true",
+ "npm_package_devDependencies__types_prosemirror_menu": "^1.0.6",
+ "npm_package_devDependencies__types_prosemirror_keymap": "^1.0.4",
+ "npm_package_devDependencies__types_pdfjs_dist": "^2.10.378",
+ "npm_package_devDependencies__types_exif": "^0.6.3",
+ "npm_package_version": "1.0.0",
+ "_CE_M": "",
+ "XPC_SERVICE_NAME": "0",
+ "npm_config_unicode": "true",
+ "npm_package_dependencies_typescript_language_server": "^0.4.0",
+ "npm_package_dependencies_prosemirror_model": "^1.18.1",
+ "npm_package_dependencies__ffmpeg_ffmpeg": "0.10.0",
+ "SHLVL": "2",
+ "HOME": "/Users/sarah",
+ "npm_config_fetch_retry_maxtimeout": "60000",
+ "npm_package_dependencies_request_promise": "^4.2.6",
+ "npm_package_dependencies_react_markdown": "^8.0.3",
+ "npm_package_dependencies__hig_theme_context": "^2.1.3",
+ "npm_package_devDependencies__types_react_autosuggest": "^9.3.14",
+ "npm_package_devDependencies__types_mongoose": "^5.11.97",
+ "npm_package_devDependencies__types_animejs": "^2.0.2",
+ "npm_package_scripts_test": "mocha -r ts-node/register test/**/*.ts",
+ "npm_config_tag_version_prefix": "v",
+ "npm_config_strict_ssl": "true",
+ "npm_config_sso_type": "oauth",
+ "npm_config_scripts_prepend_node_path": "warn-only",
+ "npm_config_save_prefix": "^",
+ "npm_config_loglevel": "notice",
+ "npm_config_ca": "",
+ "npm_package_dependencies_three": "^0.127.0",
+ "npm_package_dependencies_mobx_react": "^5.4.4",
+ "npm_package_dependencies_cookie_parser": "^1.4.6",
+ "npm_package_dependencies_adm_zip": "^0.4.16",
+ "npm_package_devDependencies_eslint_config_node": "^4.1.0",
+ "npm_config_save_exact": "",
+ "npm_config_group": "20",
+ "npm_config_fetch_retry_factor": "10",
+ "npm_config_dev": "",
+ "npm_package_devDependencies_webpack_hot_middleware": "^2.25.1",
+ "npm_package_devDependencies_cross_env": "^5.2.1",
+ "npm_config_version": "",
+ "npm_config_prefer_offline": "",
+ "npm_config_cache_lock_stale": "60000",
+ "npm_package_devDependencies__types_prosemirror_state": "^1.2.8",
+ "npm_package_devDependencies__types_body_parser": "^1.19.2",
+ "npm_config_otp": "",
+ "npm_config_cache_min": "10",
+ "npm_package_dependencies_react_color": "^2.19.3",
+ "npm_package_devDependencies_ts_node": "^10.9.1",
+ "npm_package_devDependencies__types_react_grid_layout": "^1.3.2",
+ "npm_config_searchexclude": "",
+ "npm_config_cache": "/Users/sarah/.npm",
+ "npm_package_dependencies_tough_cookie": "^4.0.0",
+ "npm_package_dependencies_googleapis": "^40.0.0",
+ "npm_package_devDependencies__types_valid_url": "^1.0.3",
+ "npm_package_devDependencies__types_passport": "^1.0.9",
+ "npm_package_devDependencies__types_adm_zip": "^0.4.34",
+ "CONDA_PYTHON_EXE": "/Users/sarah/miniconda3/bin/python",
+ "LOGNAME": "sarah",
+ "npm_lifecycle_script": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
+ "npm_config_color": "true",
+ "npm_package_dependencies_solr_node": "^1.2.1",
+ "npm_package_dependencies_react_transition_group": "^4.4.2",
+ "npm_package_dependencies_iink_js": "^1.5.4",
+ "npm_package_dependencies_html_webpack_plugin": "^5.5.0",
+ "npm_config_proxy": "",
+ "npm_config_package_lock": "true",
+ "npm_package_dependencies_prosemirror_state": "^1.4.1",
+ "npm_package_dependencies_nodemon": "^1.19.4",
+ "npm_package_dependencies_function_plot": "^1.22.8",
+ "npm_package_dependencies_equation_editor_react": "github:bobzel/equation-editor-react#useLocally",
+ "npm_package_devDependencies__types_socket_io_parser": "^3.0.0",
+ "CLASSPATH": "/Users/sarah/Downloads/cs15/*:.",
+ "npm_config_package_lock_only": "",
+ "npm_config_fund": "true",
+ "npm_package_dependencies_react": "^18.2.0",
+ "npm_package_dependencies_bingmaps_react": "^1.2.10",
+ "npm_package_devDependencies_scss_loader": "0.0.1",
+ "npm_package_devDependencies__types_cookie_session": "^2.0.44",
+ "npm_config_save_optional": "",
+ "npm_package_dependencies_textarea_caret": "^3.1.0",
+ "npm_package_dependencies_react_measure": "^2.5.2",
+ "npm_package_dependencies_exif": "^0.6.0",
+ "NVM_BIN": "/Users/sarah/.nvm/versions/node/v12.16.0/bin",
+ "CONDA_DEFAULT_ENV": "base",
+ "npm_config_ignore_scripts": "",
+ "npm_config_user_agent": "npm/6.14.7 node/v12.16.0 darwin x64",
+ "npm_package_dependencies_react_resizable": "^1.11.1",
+ "npm_package_dependencies_prosemirror_commands": "^1.2.1",
+ "npm_package_dependencies_memorystream": "^0.3.1",
+ "npm_package_dependencies_formidable": "1.2.1",
+ "npm_package_devDependencies__types_uuid": "^3.4.10",
+ "npm_config_cache_lock_wait": "10000",
+ "npm_package_dependencies_socket_io_client": "^2.5.0",
+ "npm_package_dependencies_fluent_ffmpeg": "^2.1.2",
+ "npm_package_dependencies__types_cors": "^2.8.12",
+ "npm_package_devDependencies__types_node": "^10.17.60",
+ "npm_package_devDependencies__types_file_saver": "^2.0.5",
+ "npm_config_production": "",
+ "npm_package_dependencies_jsonschema": "^1.4.0",
+ "npm_package_dependencies_ffmpeg": "0.0.4",
+ "npm_package_dependencies_cookie_session": "^2.0.0",
+ "npm_package_dependencies_color": "^3.2.1",
+ "npm_package_devDependencies__types_webpack": "^4.41.32",
+ "npm_package_devDependencies__types_request_promise": "^4.1.48",
+ "npm_package_devDependencies__types_prosemirror_schema_list": "^1.0.3",
+ "npm_config_send_metrics": "",
+ "npm_config_save_bundle": "",
+ "npm_package_dependencies_web_request": "^1.0.7",
+ "npm_package_dependencies_react_datepicker": "^3.8.0",
+ "npm_package_dependencies_express": "^4.17.3",
+ "npm_package_dependencies_D": "^1.0.0",
+ "npm_package_dependencies__types_formidable": "1.0.31",
+ "npm_package_devDependencies__types_rc_switch": "^1.9.2",
+ "npm_package_devDependencies__types_prosemirror_dev_tools": "^2.1.0",
+ "npm_package_devDependencies__types_jquery": "^3.5.14",
+ "npm_config_umask": "0022",
+ "npm_config_node_options": "",
+ "npm_config_init_version": "1.0.0",
+ "npm_package_dependencies_https": "^1.0.0",
+ "npm_package_dependencies_array_batcher": "^1.2.3",
+ "npm_package_dependencies__fortawesome_free_regular_svg_icons": "^5.15.4",
+ "npm_package_devDependencies__types_shelljs": "^0.8.11",
+ "npm_package_devDependencies__types_libxmljs": "^0.18.7",
+ "npm_package_devDependencies__types_express_validator": "^3.0.0",
+ "npm_package_devDependencies__types_bluebird": "^3.5.36",
+ "npm_config_init_author_name": "",
+ "npm_config_git": "git",
+ "npm_config_scope": "",
+ "npm_package_dependencies_react_select": "^3.2.0",
+ "npm_package_dependencies_pdf_parse": "^1.1.1",
+ "npm_package_dependencies_colors": "^1.4.0",
+ "npm_package_dependencies_archiver": "^3.1.1",
+ "npm_package_devDependencies_css_loader": "^2.1.1",
+ "npm_package_devDependencies__types_socket_io_client": "^1.4.36",
+ "npm_config_unsafe_perm": "true",
+ "npm_config_tmp": "/var/folders/yk/p_39q8jn673c5p8_66mcxm7r0000gn/T",
+ "npm_config_onload_script": "",
+ "npm_package_dependencies_serializr": "^1.5.4",
+ "npm_package_dependencies_fit_curve": "^0.1.7",
+ "npm_package_dependencies__webscopeio_react_textarea_autocomplete": "^4.9.1",
+ "npm_package_dependencies__types_three": "^0.126.2",
+ "npm_package_devDependencies_ts_node_dev": "^2.0.0",
+ "npm_node_execpath": "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
+ "npm_config_prefix": "/Users/sarah/.nvm/versions/node/v12.16.0",
+ "npm_config_link": "",
+ "npm_config_format_package_lock": "true",
+ "npm_package_dependencies_passport": "^0.4.0",
+ "npm_package_devDependencies_eslint_plugin_react": "^7.30.1",
+ "npm_package_devDependencies__types_react_table": "^6.8.9",
+ "npm_package_devDependencies__types_react_reconciler": "^0.26.4",
+ "NODE_OPTIONS": "--max_old_space_size=4096",
+ "TS_NODE_DEV": "true",
+ "VIPSHOME": "/usr/local/Cellar/vips/8.8.1",
+ "TYPESCRIPT_PATH": "/Users/sarah/Desktop/dash/Dash-Web/node_modules/typescript/lib/typescript.js",
+ "TSCONFIG": "/Users/sarah/Desktop/dash/Dash-Web/tsconfig.json",
+ "COMPILER_OPTIONS": "{}",
+ "TSLINT": "true",
+ "CONTEXT": "/Users/sarah/Desktop/dash/Dash-Web",
+ "TSLINTAUTOFIX": "false",
+ "ESLINT": "false",
+ "ESLINT_OPTIONS": "{}",
+ "WATCH": "",
+ "WORK_DIVISION": "1",
+ "MEMORY_LIMIT": "2048",
+ "CHECK_SYNTACTIC_ERRORS": "false",
+ "USE_INCREMENTAL_API": "true",
+ "VUE": "false"
+ },
+ "userLimits": {
+ "core_file_size_blocks": {
+ "soft": 0,
+ "hard": "unlimited"
+ },
+ "data_seg_size_kbytes": {
+ "soft": "unlimited",
+ "hard": "unlimited"
+ },
+ "file_size_blocks": {
+ "soft": "unlimited",
+ "hard": "unlimited"
+ },
+ "max_locked_memory_bytes": {
+ "soft": "unlimited",
+ "hard": "unlimited"
+ },
+ "max_memory_size_kbytes": {
+ "soft": "unlimited",
+ "hard": "unlimited"
+ },
+ "open_files": {
+ "soft": 1048575,
+ "hard": "unlimited"
+ },
+ "stack_size_bytes": {
+ "soft": 8388608,
+ "hard": 67104768
+ },
+ "cpu_time_seconds": {
+ "soft": "unlimited",
+ "hard": "unlimited"
+ },
+ "max_user_processes": {
+ "soft": 2784,
+ "hard": 4176
+ },
+ "virtual_memory_kbytes": {
+ "soft": "unlimited",
+ "hard": "unlimited"
+ }
+ },
+ "sharedObjects": [
+ "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
+ "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
+ "/usr/lib/libSystem.B.dylib",
+ "/usr/lib/libc++.1.dylib",
+ "/usr/lib/libobjc.A.dylib",
+ "/usr/lib/liboah.dylib",
+ "/usr/lib/libfakelink.dylib",
+ "/usr/lib/libicucore.A.dylib",
+ "/System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking",
+ "/usr/lib/libc++abi.dylib",
+ "/usr/lib/system/libcache.dylib",
+ "/usr/lib/system/libcommonCrypto.dylib",
+ "/usr/lib/system/libcompiler_rt.dylib",
+ "/usr/lib/system/libcopyfile.dylib",
+ "/usr/lib/system/libcorecrypto.dylib",
+ "/usr/lib/system/libdispatch.dylib",
+ "/usr/lib/system/libdyld.dylib",
+ "/usr/lib/system/libkeymgr.dylib",
+ "/usr/lib/system/liblaunch.dylib",
+ "/usr/lib/system/libmacho.dylib",
+ "/usr/lib/system/libquarantine.dylib",
+ "/usr/lib/system/libremovefile.dylib",
+ "/usr/lib/system/libsystem_asl.dylib",
+ "/usr/lib/system/libsystem_blocks.dylib",
+ "/usr/lib/system/libsystem_c.dylib",
+ "/usr/lib/system/libsystem_collections.dylib",
+ "/usr/lib/system/libsystem_configuration.dylib",
+ "/usr/lib/system/libsystem_containermanager.dylib",
+ "/usr/lib/system/libsystem_coreservices.dylib",
+ "/usr/lib/system/libsystem_darwin.dylib",
+ "/usr/lib/system/libsystem_dnssd.dylib",
+ "/usr/lib/system/libsystem_featureflags.dylib",
+ "/usr/lib/system/libsystem_info.dylib",
+ "/usr/lib/system/libsystem_m.dylib",
+ "/usr/lib/system/libsystem_malloc.dylib",
+ "/usr/lib/system/libsystem_networkextension.dylib",
+ "/usr/lib/system/libsystem_notify.dylib",
+ "/usr/lib/system/libsystem_product_info_filter.dylib",
+ "/usr/lib/system/libsystem_sandbox.dylib",
+ "/usr/lib/system/libsystem_secinit.dylib",
+ "/usr/lib/system/libsystem_kernel.dylib",
+ "/usr/lib/system/libsystem_platform.dylib",
+ "/usr/lib/system/libsystem_pthread.dylib",
+ "/usr/lib/system/libsystem_symptoms.dylib",
+ "/usr/lib/system/libsystem_trace.dylib",
+ "/usr/lib/system/libunwind.dylib",
+ "/usr/lib/system/libxpc.dylib",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
+ "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics",
+ "/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText",
+ "/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO",
+ "/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis",
+ "/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
+ "/System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
+ "/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface",
+ "/usr/lib/libxml2.2.dylib",
+ "/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork",
+ "/usr/lib/libz.1.dylib",
+ "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation",
+ "/System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices",
+ "/usr/lib/libMobileGestalt.dylib",
+ "/System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient",
+ "/usr/lib/libcompression.dylib",
+ "/usr/lib/libDiagnosticMessagesClient.dylib",
+ "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
+ "/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay",
+ "/System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia",
+ "/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator",
+ "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
+ "/System/Library/Frameworks/Metal.framework/Versions/A/Metal",
+ "/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
+ "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders",
+ "/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
+ "/System/Library/Frameworks/Security.framework/Versions/A/Security",
+ "/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
+ "/usr/lib/libbsm.0.dylib",
+ "/System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics",
+ "/System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox",
+ "/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices",
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList",
+ "/usr/lib/libapple_nghttp2.dylib",
+ "/usr/lib/libnetwork.dylib",
+ "/usr/lib/libsqlite3.dylib",
+ "/usr/lib/libenergytrace.dylib",
+ "/usr/lib/system/libkxld.dylib",
+ "/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression",
+ "/usr/lib/libcoretls.dylib",
+ "/usr/lib/libcoretls_cfhelpers.dylib",
+ "/usr/lib/libpam.2.dylib",
+ "/usr/lib/libxar.1.dylib",
+ "/System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout",
+ "/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration",
+ "/usr/lib/libarchive.2.dylib",
+ "/usr/lib/liblangid.dylib",
+ "/usr/lib/libCRFSuite.dylib",
+ "/usr/lib/libpcap.A.dylib",
+ "/usr/lib/libdns_services.dylib",
+ "/usr/lib/liblzma.5.dylib",
+ "/usr/lib/libbz2.1.0.dylib",
+ "/usr/lib/libiconv.2.dylib",
+ "/usr/lib/libcharset.1.dylib",
+ "/System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo",
+ "/System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer",
+ "/usr/lib/libCheckFix.dylib",
+ "/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC",
+ "/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP",
+ "/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities",
+ "/usr/lib/libmecabra.dylib",
+ "/System/Library/Frameworks/MLCompute.framework/Versions/A/MLCompute",
+ "/usr/lib/libmecab.dylib",
+ "/usr/lib/libgermantok.dylib",
+ "/usr/lib/libThaiTokenizer.dylib",
+ "/usr/lib/libChineseTokenizer.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib",
+ "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib",
+ "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore",
+ "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage",
+ "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork",
+ "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix",
+ "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector",
+ "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray",
+ "/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools",
+ "/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary",
+ "/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib",
+ "/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling",
+ "/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji",
+ "/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData",
+ "/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon",
+ "/usr/lib/libcmph.dylib",
+ "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory",
+ "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory",
+ "/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS",
+ "/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation",
+ "/usr/lib/libutil.dylib",
+ "/usr/lib/libapp_launch_measurement.dylib",
+ "/System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore",
+ "/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement",
+ "/usr/lib/libxslt.1.dylib",
+ "/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement",
+ "/System/Library/PrivateFrameworks/PersistentConnection.framework/Versions/A/PersistentConnection",
+ "/System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer",
+ "/System/Library/PrivateFrameworks/CommonUtilities.framework/Versions/A/CommonUtilities",
+ "/System/Library/PrivateFrameworks/Bom.framework/Versions/A/Bom",
+ "/usr/lib/libate.dylib",
+ "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib",
+ "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib",
+ "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib",
+ "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib",
+ "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib",
+ "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib",
+ "/usr/lib/libexpat.1.dylib",
+ "/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG",
+ "/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler",
+ "/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment",
+ "/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay",
+ "/System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore",
+ "/usr/lib/libspindump.dylib",
+ "/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio",
+ "/System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport",
+ "/System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata",
+ "/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices",
+ "/System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore",
+ "/System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk",
+ "/System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy",
+ "/usr/lib/libIOReport.dylib",
+ "/usr/lib/libSMC.dylib",
+ "/usr/lib/libAudioToolboxUtility.dylib",
+ "/usr/lib/libmis.dylib",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib",
+ "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib",
+ "/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage",
+ "/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL",
+ "/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer",
+ "/System/Library/PrivateFrameworks/FaceCore.framework/Versions/A/FaceCore",
+ "/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib",
+ "/System/Library/PrivateFrameworks/FontServices.framework/libhvf.dylib",
+ "/System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA",
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI",
+ "/usr/lib/libcups.2.dylib",
+ "/System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth",
+ "/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos",
+ "/System/Library/Frameworks/GSS.framework/Versions/A/GSS",
+ "/usr/lib/libresolv.9.dylib",
+ "/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal",
+ "/System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib",
+ "/System/Library/Frameworks/Network.framework/Versions/A/Network",
+ "/usr/lib/libheimdal-asn1.dylib",
+ "/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth",
+ "/System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport",
+ "/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox",
+ "/System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession",
+ "/usr/lib/libAudioStatistics.dylib",
+ "/System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience",
+ "/System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib",
+ "/usr/lib/libperfcheck.dylib",
+ "/System/Library/PrivateFrameworks/AudioResourceArbitration.framework/Versions/A/AudioResourceArbitration",
+ "/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData",
+ "/Users/sarah/Desktop/dash/Dash-Web/node_modules/fsevents/build/Release/fse.node"
+ ]
+} \ No newline at end of file
diff --git a/src/.DS_Store b/src/.DS_Store
index be99aa5af..06389d6ae 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index f2ad814d4..7f83ab8f5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -6,6 +6,7 @@ import { Socket } from 'socket.io';
import { Colors } from './client/views/global/globalEnums';
import { Message } from './server/Message';
import Color = require('color');
+import { DocumentType } from './client/documents/DocumentTypes';
export namespace Utils {
export let CLICK_TIME = 300;
@@ -15,6 +16,21 @@ export namespace Utils {
return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD;
}
+ export function cleanDocumentType(type: DocumentType) {
+ switch (type) {
+ case DocumentType.IMG:
+ return 'Image';
+ case DocumentType.AUDIO:
+ return 'Audio';
+ case DocumentType.COL:
+ return 'Collection';
+ case DocumentType.RTF:
+ return 'Text';
+ default:
+ return type.charAt(0).toUpperCase() + type.slice(1);
+ }
+ }
+
export function readUploadedFileAsText(inputFile: File) {
const temporaryFileReader = new FileReader();
@@ -60,11 +76,11 @@ export namespace Utils {
});
return returnedUri;
} catch (e) {
- console.log('VideoBox :' + e);
+ console.log('ConvertDataURI :' + e);
}
}
- export function GetScreenTransform(ele?: HTMLElement): { scale: number; translateX: number; translateY: number } {
+ export function GetScreenTransform(ele?: HTMLElement | null): { scale: number; translateX: number; translateY: number } {
if (!ele) {
return { scale: 1, translateX: 1, translateY: 1 };
}
@@ -124,7 +140,7 @@ export namespace Utils {
}
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.length < 8 ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex;
}
export function fromRGBAstr(rgba: string) {
@@ -141,7 +157,7 @@ export namespace Utils {
const isTransparentFunctionHack = 'isTransparent(__value__)';
export const noRecursionHack = '__noRecursion';
- export const noDragsDocFilter = 'noDragDocs:any:check';
+ export const noDragsDocFilter = 'noDragDocs::any::check';
export function IsRecursiveFilter(val: string) {
return !val.includes(noRecursionHack);
}
@@ -150,14 +166,14 @@ 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 IsPropUnsetFilter(prop: string) {
- return `${prop}:any,${noRecursionHack}:unset`;
+ return `${prop}::any,${noRecursionHack}::unset`;
}
export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
@@ -527,6 +543,10 @@ export function returnDefault(): 'default' {
return 'default';
}
+export function return18() {
+ return 18;
+}
+
export function returnFalse() {
return false;
}
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 02b047a95..53c7b857a 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,16 +1,18 @@
import { runInAction } from 'mobx';
import * as rp from 'request-promise';
import * as io from 'socket.io-client';
-import { Doc, Opt } from '../fields/Doc';
+import { Doc, DocListCast, Opt } from '../fields/Doc';
import { UpdatingFromServer } from '../fields/DocSymbols';
import { FieldLoader } from '../fields/FieldLoader';
import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
import { ObjectField } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
-import { StrCast } from '../fields/Types';
+import { DocCast, StrCast } from '../fields/Types';
import MobileInkOverlay from '../mobile/MobileInkOverlay';
import { emptyFunction, Utils } from '../Utils';
import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
+import { DocumentType } from './documents/DocumentTypes';
+import { LinkManager } from './util/LinkManager';
import { SerializationHelper } from './util/SerializationHelper';
import { GestureOverlay } from './views/GestureOverlay';
@@ -30,38 +32,51 @@ import { GestureOverlay } from './views/GestureOverlay';
export namespace DocServer {
let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
- export function QUERY_SERVER_CACHE(title: string) {
+ export function FindDocByTitle(title: string) {
const foundDocId = Array.from(Object.keys(_cache))
.filter(key => _cache[key] instanceof Doc)
.find(key => (_cache[key] as Doc).title === title);
return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
}
- export function UPDATE_SERVER_CACHE(print: boolean = false) {
- if (print) {
- const strings: string[] = [];
- 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)));
- });
- strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
- }
- const filtered = Array.from(Object.keys(_cache)).filter(key => {
- const doc = _cache[key] as Doc;
- if (!(StrCast(doc.author).includes('.edu') || StrCast(doc.author).includes('.com')) || doc.author == Doc.CurrentUserEmail) return true;
- return false;
+ let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
+ export let CacheNeedsUpdate = false;
+ export function UPDATE_SERVER_CACHE() {
+ const prototypes = Object.values(DocumentType)
+ .filter(type => type !== DocumentType.NONE)
+ .map(type => _cache[type + 'Proto'])
+ .filter(doc => doc instanceof Doc)
+ .map(doc => doc as Doc);
+ const references = new Set<Doc>(prototypes);
+ Doc.FindReferences(Doc.UserDoc(), references, undefined);
+ DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
+ if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
+ Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
+ }
});
+ LinkManager.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
+ const filtered = Array.from(references);
+
+ const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
+ if (newCacheUpdate === cacheDocumentIds) return;
+ cacheDocumentIds = newCacheUpdate;
+
+ // print out cached docs
+ console.log('Set cached docs = ');
+ const is_filtered = filtered.filter(doc => !Doc.IsSystem(doc));
+ const strings = is_filtered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
+ strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
rp.post(Utils.prepend('/setCacheDocumentIds'), {
body: {
- cacheDocumentIds: filtered.join(';'),
+ cacheDocumentIds,
},
json: true,
});
}
export let _socket: SocketIOClient.Socket;
// this client's distinct GUID created at initialization
- let GUID: string;
+ let USER_ID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
export enum WriteMode {
@@ -73,10 +88,14 @@ export namespace DocServer {
const fieldWriteModes: { [field: string]: WriteMode } = {};
const docsWithUpdates: { [field: string]: Set<Doc> } = {};
- export var PlaygroundFields: string[];
- export function setPlaygroundFields(livePlaygroundFields: string[]) {
- DocServer.PlaygroundFields = livePlaygroundFields;
- livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground));
+ export var PlaygroundFields: string[] = [];
+ export function setLivePlaygroundFields(livePlaygroundFields: string[]) {
+ DocServer.PlaygroundFields.push(...livePlaygroundFields);
+ livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
+ }
+ export function setPlaygroundFields(playgroundFields: string[]) {
+ DocServer.PlaygroundFields.push(...playgroundFields);
+ playgroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground));
}
export function IsPlaygroundField(field: string) {
return DocServer.PlaygroundFields?.includes(field.replace(/^_/, ''));
@@ -94,7 +113,7 @@ export namespace DocServer {
}
export function getFieldWriteMode(field: string) {
- return Doc.CurrentUserEmail === 'guest' ? WriteMode.LiveReadonly : fieldWriteModes[field] || WriteMode.Default;
+ return Doc.CurrentUserEmail === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
}
export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -147,9 +166,9 @@ export namespace DocServer {
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
- GUID = identifier;
+ USER_ID = identifier;
protocol = protocol.startsWith('https') ? 'wss' : 'ws';
- _socket = io.connect(`${protocol}://${hostname}:${port}`);
+ _socket = require('socket.io-client')(`${protocol}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
// io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
_GetCachedRefField = _GetCachedRefFieldImpl;
@@ -200,7 +219,7 @@ export namespace DocServer {
}
export function makeEditable() {
- if (_isReadOnly) {
+ if (Control.isReadOnly()) {
location.reload();
// _isReadOnly = false;
// _CreateField = _CreateFieldImpl;
@@ -221,7 +240,7 @@ export namespace DocServer {
* indicating that this client has connected
*/
function onConnection() {
- _socket.emit(MessageStore.Bar.Message, GUID);
+ _socket.emit(MessageStore.Bar.Message, USER_ID);
}
export namespace Util {
@@ -345,7 +364,7 @@ export namespace DocServer {
// i) which documents need to be fetched
// ii) which are already in the process of being fetched
// iii) which already exist in the cache
- for (const id of ids) {
+ for (const id of ids.filter(id => id)) {
const cached = _cache[id];
if (cached === undefined) {
defaultPromises.push({
@@ -374,7 +393,7 @@ export namespace DocServer {
// fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of
// the fields have been returned from the server
console.log('Requesting ' + requestedIds.length);
- FieldLoader.active && runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length));
+ setTimeout(() => runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length)));
const serializedFields = await Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
// 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
@@ -384,7 +403,7 @@ export namespace DocServer {
console.log('deserializing ' + serializedFields.length + ' fields');
for (const field of serializedFields) {
processed++;
- if (FieldLoader.active && processed % 150 === 0) {
+ if (processed % 150 === 0) {
runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = processed));
await new Promise(res => setTimeout(res)); // force loading to yield to splash screen rendering to update progress
}
@@ -470,13 +489,14 @@ export namespace DocServer {
* @param field the [RefField] to be serialized and sent to the server to be stored in the database
*/
export function CreateField(field: RefField) {
+ CacheNeedsUpdate = true;
_CreateField(field);
}
function _CreateFieldImpl(field: RefField) {
_cache[field[Id]] = field;
const initialState = SerializationHelper.Serialize(field);
- Utils.Emit(_socket, MessageStore.CreateField, initialState);
+ Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.CreateField, initialState);
}
let _CreateField: (field: RefField) => void = errorFunc;
@@ -496,7 +516,7 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- !DocServer.Control.isReadOnly() && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
let _UpdateField: (id: string, diff: any) => void = errorFunc;
@@ -533,11 +553,11 @@ export namespace DocServer {
}
export function DeleteDocument(id: string) {
- Utils.Emit(_socket, MessageStore.DeleteField, id);
+ Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteField, id);
}
export function DeleteDocuments(ids: string[]) {
- Utils.Emit(_socket, MessageStore.DeleteFields, ids);
+ Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteFields, ids);
}
function _respondToDeleteImpl(ids: string | string[]) {
diff --git a/src/client/Network.ts b/src/client/Network.ts
index d606b9854..39bf69e32 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -5,7 +5,7 @@ import { Upload } from '../server/SharedMediaTypes';
/**
* Networking is repsonsible for connecting the client to the server. Networking
* mainly provides methods that the client can use to begin the process of
- * interacting with the server, such as fetching or uploading files.
+ * interacting with the server, such as fetching or uploading files.
*/
export namespace Networking {
export async function FetchFromServer(relativeRoute: string) {
@@ -25,9 +25,9 @@ export namespace Networking {
/**
* FileGuidPair attaches a guid to a file that is being uploaded,
* allowing the client to track the upload progress.
- *
+ *
* When files are dragged to the canvas, the overWriteDoc's ID is
- * used as the guid. Otherwise, a new guid is generated.
+ * used as the guid. Otherwise, a new guid is generated.
*/
export interface FileGuidPair {
file: File;
@@ -38,9 +38,10 @@ export namespace Networking {
* with the mapping of guid to files as parameters.
*
* @param fileguidpairs the files and corresponding guids to be uploaded to the server
+ * @param browndash whether the endpoint should be invoked on the browndash server
* @returns the response as a json from the server
*/
- export async function UploadFilesToServer<T extends Upload.FileInformation = Upload.FileInformation>(fileguidpairs: FileGuidPair | FileGuidPair[]): Promise<Upload.FileResponse<T>[]> {
+ export async function UploadFilesToServer<T extends Upload.FileInformation = Upload.FileInformation>(fileguidpairs: FileGuidPair | FileGuidPair[], browndash?: boolean): Promise<Upload.FileResponse<T>[]> {
const formData = new FormData();
if (Array.isArray(fileguidpairs)) {
if (!fileguidpairs.length) {
@@ -57,17 +58,19 @@ export namespace Networking {
])
);
}
- // If the fileguidpair has a guid to use (From the overwriteDoc) use that guid. Otherwise, generate a new guid.
+ // If the fileguidpair has a guid to use (From the overwriteDoc) use that guid. Otherwise, generate a new guid.
fileguidpairs.forEach(fileguidpair => formData.append(fileguidpair.guid ?? Utils.GenerateGuid(), fileguidpair.file));
} else {
- // Handle the case where fileguidpairs is a single file.
+ // Handle the case where fileguidpairs is a single file.
formData.append(fileguidpairs.guid ?? Utils.GenerateGuid(), fileguidpairs.file);
}
const parameters = {
method: 'POST',
body: formData,
};
- const response = await fetch('/uploadFormData', parameters);
+
+ const endpoint = browndash ? '[insert endpoint allowing local => browndash]' : '/uploadFormData';
+ const response = await fetch(endpoint, parameters);
return response.json();
}
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index cdc8c275c..1d0ddce40 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -2,7 +2,7 @@ export enum DocumentType {
NONE = 'none',
// core data types
- RTF = 'rtf',
+ RTF = 'rich text',
IMG = 'image',
WEB = 'web',
COL = 'collection',
@@ -14,13 +14,11 @@ export enum DocumentType {
INK = 'ink',
SCREENSHOT = 'screenshot',
FONTICON = 'fonticonbox',
- FILTER = 'filter',
SEARCH = 'search', // search query
LABEL = 'label', // simple text label
BUTTON = 'button', // onClick button
WEBCAM = 'webcam', // webcam
CONFIG = 'config', // configuration document intended to specify a view layout configuration, but not be directly rendered (e.g., for saving the page# of a PDF, or view transform of a collection)
- DATE = 'date', // calendar view of a date
SCRIPTING = 'script', // script editor
EQUATION = 'equation', // equation editor
FUNCPLOT = 'funcplot', // function plotter
@@ -31,18 +29,15 @@ export enum DocumentType {
// 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',
- LINKDB = 'linkdb', // database of links ??? why do we have this
SCRIPTDB = 'scriptdb', // database of scripts
GROUPDB = 'groupdb', // database of groups
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a91d5806c..e7d3e7efc 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,5 +1,5 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { action, runInAction } from 'mobx';
+import { action, reaction, runInAction } from 'mobx';
import { basename } from 'path';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Field, Opt, updateCachedAcls } from '../../fields/Doc';
@@ -25,7 +25,7 @@ import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox';
import { FollowLinkScript } from '../util/LinkFollower';
import { LinkManager } from '../util/LinkManager';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
+import { undoable, UndoManager } from '../util/UndoManager';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
import { CollectionView } from '../views/collections/CollectionView';
@@ -34,7 +34,7 @@ import { ContextMenuProps } from '../views/ContextMenuItem';
import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
import { AudioBox } from '../views/nodes/AudioBox';
-import { FontIconBox } from '../views/nodes/button/FontIconBox';
+import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
import { ColorBox } from '../views/nodes/ColorBox';
import { ComparisonBox } from '../views/nodes/ComparisonBox';
import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
@@ -169,22 +169,25 @@ export class DocumentOptions {
_nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers');
_nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
- 'acl-Public'?: string; // public permissions
- '_acl-Public'?: string; // public permissions
+ 'acl-Guest'?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions
+ '_acl-Guest'?: string; // public permissions
type?: DTYPEt = new DTypeInfo('type of document', true);
+ type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
_type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
title?: STRt = new StrInfo('title of document');
caption?: RichTextField;
author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable
author_date?: DATEt = new DateInfo('date the document was created', true);
annotationOn?: DOCt = new DocInfo('document annotated by this document');
+ embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument');
color?: STRt = new StrInfo('foreground color data doc');
hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection');
backgroundColor?: STRt = new StrInfo('background color for data doc');
opacity?: NUMt = new NumInfo('document opacity');
viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters');
dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc');
- dontUndo?: BOOLt = new BoolInfo('whether button clicks should be undoable (true for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)');
+ _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
+ undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
_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');
@@ -286,12 +289,12 @@ export class DocumentOptions {
_isTimelineLabel?: BOOLt = new BoolInfo('is document a timeline label');
_isLightbox?: BOOLt = new BoolInfo('whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target');
- presPanX?: NUMt = new NumInfo('panX saved as a view spec');
- presPanY?: NUMt = new NumInfo('panY saved as a view spec');
- presViewScale?: NUMt = new NumInfo('viewScale saved as a view Spec');
- presTransition?: NUMt = new NumInfo('the time taken for the transition TO a document');
- presDuration?: NUMt = new NumInfo('the duration of the slide in presentation view');
- presZoomText?: BOOLt = new BoolInfo('whether text anchors should shown in a larger box when following links to make them stand out');
+ config_panX?: NUMt = new NumInfo('panX saved as a view spec');
+ config_panY?: NUMt = new NumInfo('panY saved as a view spec');
+ config_viewScale?: NUMt = new NumInfo('viewScale saved as a view Spec');
+ presentation_transition?: NUMt = new NumInfo('the time taken for the transition TO a document');
+ presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view');
+ presentation_zoomText?: BOOLt = new BoolInfo('whether text anchors should shown in a larger box when following links to make them stand out');
data?: any;
data_useCors?: BOOLt = new BoolInfo('whether CORS protocol should be used for web page');
@@ -302,7 +305,7 @@ export class DocumentOptions {
noteType?: string;
// freeform properties
- _freeform_backgroundGrid?: boolean;
+ _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections');
_freeform_scale?: NUMt = new NumInfo('how much a freeform view has been scaled (zoomed)');
_freeform_panX?: NUMt = new NumInfo('horizontal pan location of a freeform view');
_freeform_panY?: NUMt = new NumInfo('vertical pan location of a freeform view');
@@ -322,8 +325,9 @@ export class DocumentOptions {
badgeValue?: ScriptField;
//LINEAR VIEW
- linearView_IsExpanded?: BOOLt = new BoolInfo('is linear view expanded');
+ linearView_IsOpen?: BOOLt = new BoolInfo('is linear view open');
linearView_Expandable?: BOOLt = new BoolInfo('can linear view be expanded');
+ linearView_Dropdown?: BOOLt = new BoolInfo('can linear view be opened as a dropdown');
linearView_SubMenu?: BOOLt = new BoolInfo('is doc a sub menu of more linear views');
flexGap?: NUMt = new NumInfo('Linear view flex gap');
flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse';
@@ -332,8 +336,8 @@ export class DocumentOptions {
link_relationship?: string; // type of relatinoship a link represents
link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors');
link_displayArrow?: BOOLt = new BoolInfo("whether to display link's directional arrowhead");
- link_anchor_1?: Doc;
- link_anchor_2?: Doc;
+ link_anchor_1?: DOCt = new DocInfo('start anchor of a link');
+ link_anchor_2?: DOCt = new DocInfo('end anchor of a link');
link_autoMoveAnchors?: BOOLt = new BoolInfo('whether link endpoint should move around the edges of a document to make shortest path to other link endpoint');
link_anchor_1_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
link_anchor_2_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
@@ -351,48 +355,50 @@ export class DocumentOptions {
onDoubleClick?: ScriptField;
onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked
+ onClickScriptDisable?: STRt = new StrInfo('"always" disable click script, "never" disable click script, or default');
defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
onPointerDown?: ScriptField;
onPointerUp?: ScriptField;
- dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true);
openFactoryLocation?: string; // an OpenWhere value to place the factory created document
openFactoryAsDelegate?: BOOLt = new BoolInfo('create a delegate of the factory');
_forceActive?: BOOLt = new BoolInfo('flag to handle pointer events when not selected (or otherwise active)');
- enableDragWhenActive?: BOOLt = new BoolInfo('allow dragging even if document contentts are active (e.g., tree, groups)');
- _stayInCollection?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere');
+ _dragOnlyWithinContainer?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere');
_raiseWhenDragged?: BOOLt = new BoolInfo('whether a document is brought to front when dragged.');
- allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true);
- childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection ");
- targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? ');
+ childDragAction?: DROPt = new DAInfo('what should happen to the child documents when they are dragged from the collection');
+ dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
+ dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
_dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else");
- _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
+ _dropPropertiesToRemove?: 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
cloneFieldFilter?: List<string>; // fields not to copy when the document is clonedclipboard?: Doc;
- dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
- dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
- clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script
+ dragWhenActive?: BOOLt = new BoolInfo('should document drag when it is active - e.g., pileView, group');
+ dragAction?: DROPt = new DAInfo('how to drag document when it is active (e.g., tree, groups)');
+ dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true);
+ dragFactory?: DOCt = new DocInfo('document to create when dragging with a suitable onDragStart script');
+ clickFactory?: DOCt = new DocInfo('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
target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script)
- treeViewHideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
- treeViewHideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeViewFieldKey tag (presBox)");
- treeViewHideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)');
- treeViewHideHeader?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view');
- treeViewHideHeaderFields?: BOOLt = new BoolInfo('whether to hide the drop down options for tree view items.');
- treeViewChildDoubleClick?: ScriptField; //
- treeViewOpenIsTransient?: BOOLt = new BoolInfo("ignores the treeViewOpen Doc flag, allowing a treeViewItem's expand/collapse state to be independent of other views of the same document in the same or any other tree view");
- treeViewOpen?: BOOLt = new BoolInfo('whether this document is expanded in a tree view');
- treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view
- treeViewExpandedViewLock?: BOOLt = new BoolInfo('whether the expanded view can be changed');
- treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
- treeViewTruncateTitleWidth?: NUMt = new NumInfo('maximum width of a treew view title before truncation');
- treeViewHasOverlay?: BOOLt = new BoolInfo('whether the treeview has an overlay for freeform annotations');
- treeViewType?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy
- treeViewFreezeChildren?: STRt = new StrInfo('set (add, remove, add|remove) to disable adding, removing or both from collection');
+ treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
+ treeView_HideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeView_FieldKey tag (presBox)");
+ treeView_HideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)');
+ treeView_HideHeader?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view');
+ treeView_HideHeaderFields?: BOOLt = new BoolInfo('whether to hide the drop down options for tree view items.');
+ treeView_ChildDoubleClick?: ScriptField; //
+ treeView_OpenIsTransient?: BOOLt = new BoolInfo("ignores the treeView_Open Doc flag, allowing a treeView_Item's expand/collapse state to be independent of other views of the same document in the same or any other tree view");
+ treeView_Open?: BOOLt = new BoolInfo('whether this document is expanded in a tree view');
+ treeView_ExpandedView?: string; // which field/thing is displayed when this item is opened in tree view
+ treeView_ExpandedViewLock?: BOOLt = new BoolInfo('whether the expanded view can be changed');
+ treeView_Checked?: ScriptField; // script to call when a tree view checkbox is checked
+ treeView_TruncateTitleWidth?: NUMt = new NumInfo('maximum width of a treew view title before truncation');
+ treeView_HasOverlay?: BOOLt = new BoolInfo('whether the treeview has an overlay for freeform annotations');
+ treeView_Type?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy
+ treeView_FreezeChildren?: STRt = new StrInfo('set (add, remove, add|remove) to disable adding, removing or both from collection');
sidebar_color?: string; // background color of text sidebar
sidebar_collectionType?: string; // collection type of text sidebar
+ data_dashboards?: List<any>; // list of dashboards used in shareddocs;
text?: string;
textTransform?: string;
letterSpacing?: string;
@@ -404,6 +410,7 @@ export class DocumentOptions {
clipboard?: Doc;
hoverBackgroundColor?: string; // background color of a label when hovered
+ userBackgroundColor?: STRt = new StrInfo('background color associated with a Dash user (seen in header fields of shared documents)');
userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
}
export namespace Docs {
@@ -436,6 +443,7 @@ export namespace Docs {
nativeHeightUnfrozen: true,
layout_forceReflow: true,
defaultDoubleClick: 'ignore',
+ systemIcon: 'BsFileEarmarkTextFill',
},
},
],
@@ -457,21 +465,21 @@ export namespace Docs {
DocumentType.IMG,
{
layout: { view: ImageBox, dataField: defaultDataKey },
- options: { freeform: '' },
+ options: { freeform: '', systemIcon: 'BsFileEarmarkImageFill' },
},
],
[
DocumentType.WEB,
{
layout: { view: WebBox, dataField: defaultDataKey },
- options: { _height: 300, _layout_fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, waitForDoubleClickToClick: 'always' },
+ options: { _height: 300, _layout_fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
},
],
[
DocumentType.COL,
{
layout: { view: CollectionView, dataField: defaultDataKey },
- options: { _layout_fitWidth: true, freeform: '', _freeform_panX: 0, _freeform_panY: 0, _freeform_scale: 1 },
+ options: { _layout_fitWidth: true, freeform: '', _freeform_panX: 0, _freeform_panY: 0, _freeform_scale: 1, systemIcon: 'BsFillCollectionFill' },
},
],
[
@@ -485,35 +493,35 @@ export namespace Docs {
DocumentType.VID,
{
layout: { view: VideoBox, dataField: defaultDataKey },
- options: { _layout_currentTimecode: 0 },
+ options: { _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' },
},
],
[
DocumentType.AUDIO,
{
layout: { view: AudioBox, dataField: defaultDataKey },
- options: { _height: 100, layout_fitWidth: true, layout_forceReflow: true, nativeDimModifiable: true },
+ options: { _height: 100, layout_fitWidth: true, layout_forceReflow: true, nativeDimModifiable: true, systemIcon: 'BsFillVolumeUpFill' },
},
],
[
DocumentType.REC,
{
layout: { view: VideoBox, dataField: defaultDataKey },
- options: { _height: 100, backgroundColor: 'pink' },
+ options: { _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' },
},
],
[
DocumentType.PDF,
{
layout: { view: PDFBox, dataField: defaultDataKey },
- options: { _layout_curPage: 1, _layout_fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true },
+ options: { _layout_curPage: 1, _layout_fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, systemIcon: 'BsFileEarmarkPdfFill' },
},
],
[
DocumentType.MAP,
{
layout: { view: MapBox, dataField: defaultDataKey },
- options: { _height: 600, _width: 800, nativeDimModifiable: true },
+ options: { _height: 600, _width: 800, nativeDimModifiable: true, systemIcon: 'BsFillPinMapFill' },
},
],
[
@@ -536,31 +544,23 @@ export namespace Docs {
link_description: '',
layout_showCaption: 'link_description',
backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
- _removeDropProperties: new List(['onClick']),
+ _dropPropertiesToRemove: new List(['onClick']),
},
},
],
[
- DocumentType.LINKDB,
- {
- data: new List<Doc>(),
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { childDropAction: 'embed', title: 'Global Link Database' },
- },
- ],
- [
DocumentType.SCRIPTDB,
{
data: new List<Doc>(),
layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { childDropAction: 'embed', title: 'Global Script Database' },
+ options: { title: 'Global Script Database' },
},
],
[
DocumentType.SCRIPTING,
{
layout: { view: ScriptingBox, dataField: defaultDataKey },
- options: {},
+ options: { systemIcon: 'BsFileEarmarkCodeFill' },
},
],
[
@@ -580,7 +580,7 @@ export namespace Docs {
DocumentType.EQUATION,
{
layout: { view: EquationBox, dataField: 'text' },
- options: { nativeDimModifiable: true, fontSize: '14px', layout_hideResizeHandles: true, layout_hideDecorationTitle: true },
+ options: { nativeDimModifiable: true, fontSize: '14px', layout_hideResizeHandles: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, ///systemIcon: 'BsSuperscript' + BsSubscript
},
],
[
@@ -615,14 +615,14 @@ export namespace Docs {
DocumentType.FONTICON,
{
layout: { view: FontIconBox, dataField: 'icon' },
- options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', enableDragWhenActive: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
+ options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideLinkButton: true, _width: 40, _height: 40 },
},
],
[
DocumentType.WEBCAM,
{
layout: { view: RecordingBox, dataField: defaultDataKey },
- options: {},
+ options: { systemIcon: 'BsFillCameraVideoFill' },
},
],
[
@@ -636,7 +636,7 @@ export namespace Docs {
DocumentType.CONFIG,
{
layout: { view: CollectionView, dataField: defaultDataKey },
- options: { layout_hideLinkButton: true, pointerEvents: 'none', layout_unrendered: true },
+ options: { config: '', layout_hideLinkButton: true, layout_unrendered: true },
},
],
[
@@ -644,14 +644,14 @@ export namespace Docs {
{
// NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
layout: { view: InkingStroke, dataField: 'stroke' },
- options: {},
+ options: { systemIcon: 'BsFillPencilFill' },
},
],
[
DocumentType.SCREENSHOT,
{
layout: { view: ScreenshotBox, dataField: defaultDataKey },
- options: { nativeDimModifiable: true, nativeHeightUnfrozen: true },
+ options: { nativeDimModifiable: true, nativeHeightUnfrozen: true, systemIcon: 'BsCameraFill' },
},
],
[
@@ -659,15 +659,14 @@ export namespace Docs {
{
data: '',
layout: { view: ComparisonBox, dataField: defaultDataKey },
- options: { backgroundColor: 'gray', targetDropAction: 'embed' },
+ options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', systemIcon: 'BsLayoutSplit' },
},
],
[
DocumentType.GROUPDB,
{
- data: new List<Doc>(),
layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { childDropAction: 'embed', title: 'Global Group Database' },
+ options: { title: 'Global Group Database' },
},
],
[
@@ -681,7 +680,7 @@ export namespace Docs {
DocumentType.DATAVIZ,
{
layout: { view: DataVizBox, dataField: defaultDataKey },
- options: { _layout_fitWidth: true, nativeDimModifiable: true },
+ options: { dataViz_title: '', _layout_fitWidth: true, nativeDimModifiable: true },
},
],
[
@@ -696,7 +695,22 @@ export namespace Docs {
{
data: '',
layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 },
- options: { _height: 100, layout_forceReflow: true, nativeHeightUnfrozen: true, mass1: '', mass2: '', nativeDimModifiable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '' },
+ options: {
+ _height: 100,
+ layout_forceReflow: true,
+ nativeHeightUnfrozen: true,
+ mass1: '',
+ mass2: '',
+ nativeDimModifiable: true,
+ position: '',
+ acceleration: '',
+ pendulum: '',
+ spring: '',
+ wedge: '',
+ simulation: '',
+ review: '',
+ systemIcon: 'BsShareFill',
+ },
},
],
]);
@@ -720,7 +734,7 @@ export namespace Docs {
.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);
+ const actualProtos = await DocServer.GetRefFields(prototypeIds);
// update this object to include any default values: DocumentOptions for all prototypes
prototypeIds.map(id => {
const existing = actualProtos[id] as Doc;
@@ -731,6 +745,10 @@ export namespace Docs {
// an entry dedicated to the given DocumentType)
target && PrototypeMap.set(type, target);
});
+ reaction(
+ () => (proto => StrCast(proto?.BROADCAST_MESSAGE))(DocServer.GetCachedRefField('rtfProto') as Doc),
+ msg => msg && alert(msg)
+ );
}
/**
@@ -745,13 +763,6 @@ export namespace Docs {
}
/**
- * 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);
- }
-
- /**
* A collection of all scripts in the database
*/
export function MainScriptDocument() {
@@ -798,6 +809,7 @@ export namespace Docs {
x: 0,
y: 0,
_width: 300,
+ 'acl-Guest': SharingPermissions.View,
...(template.options || {}),
layout: layout.view?.LayoutString(layout.dataField),
data: template.data,
@@ -834,20 +846,19 @@ 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, placeholderDoc?: Doc) {
+ function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc, noView?: boolean) {
const viewKeys = ['x', 'y', 'isSystem']; // 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'] = SharingPermissions.Unset;
- dataProps['acl-Public'] = options['acl-Public'] ? options['acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
-
+ dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
dataProps.isSystem = viewProps.isSystem;
dataProps.isDataDoc = true;
dataProps.author = Doc.CurrentUserEmail;
dataProps.author_date = new DateField();
if (fieldKey) {
dataProps[`${fieldKey}_modificationDate`] = new DateField();
- dataProps[fieldKey] = data;
+ dataProps[fieldKey] = options.data ?? 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.
@@ -862,30 +873,34 @@ export namespace Docs {
dataDoc.proto = proto;
}
- const viewFirstProps: { [id: string]: any } = {};
- viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
- // viewFirstProps['acl-Override'] = SharingPermissions.Unset;
- viewFirstProps.author = Doc.CurrentUserEmail;
- let viewDoc: Doc;
- // determines whether viewDoc should be created using placeholder Doc or default
- if (placeholderDoc) {
- placeholderDoc._height = options._height !== undefined ? Number(options._height) : undefined;
- placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined;
- viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true);
- } else {
- viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true);
- }
- Doc.assign(viewDoc, viewProps, true, true);
- if (![DocumentType.LINK, DocumentType.CONFIG, DocumentType.LABEL].includes(viewDoc.type as any)) {
- DocUtils.MakeLinkToActiveAudio(() => viewDoc);
- }
+ if (!noView) {
+ const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail };
+ viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
+ let viewDoc: Doc;
+ // determines whether viewDoc should be created using placeholder Doc or default
+ if (placeholderDoc) {
+ placeholderDoc._height = options._height !== undefined ? Number(options._height) : undefined;
+ placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined;
+ viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true);
+ Array.from(Object.keys(placeholderDoc))
+ .filter(key => key.startsWith('acl'))
+ .forEach(key => (dataDoc[key] = viewDoc[key] = placeholderDoc[key]));
+ } else {
+ viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true);
+ }
+ Doc.assign(viewDoc, viewProps, true, true);
+ if (![DocumentType.LINK, DocumentType.CONFIG, DocumentType.LABEL].includes(viewDoc.type as any)) {
+ DocUtils.MakeLinkToActiveAudio(() => viewDoc);
+ }
+ updateCachedAcls(dataDoc);
+ updateCachedAcls(viewDoc);
- Doc.AddFileOrphan(dataDoc);
+ return viewDoc;
+ }
updateCachedAcls(dataDoc);
- updateCachedAcls(viewDoc);
- return viewDoc;
+ return dataDoc;
}
export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
@@ -1011,10 +1026,11 @@ export namespace Docs {
I.rotation = 0;
I.defaultDoubleClick = 'click';
I.author_date = new DateField();
- I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+ I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
//I['acl-Override'] = SharingPermissions.Unset;
I[Initializing] = false;
- return I;
+
+ return InstanceFromProto(I, '', options);
}
export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
@@ -1050,39 +1066,12 @@ export namespace Docs {
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _type_collection: CollectionViewType.Freeform }, id);
- documents.forEach(d => (d.embedContainer = inst));
+ documents.forEach(d => Doc.SetContainer(d, inst));
return inst;
}
- export function WebConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), undefined, options, id);
- }
- export function CollectionConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function PdfConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function TextConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function FunctionPlotConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function DataVizConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function LineChartConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function ComparisonConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function ImageConfigDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
- }
- export function InkConfigDocument(options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id);
+ export function ConfigDocument(options: DocumentOptions, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.CONFIG), options?.data, options, id, '', undefined, undefined, true);
}
export function HTMLMarkerDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -1096,7 +1085,7 @@ export namespace Docs {
return InstanceFromProto(
Prototypes.get(DocumentType.COL),
new List(documents),
- { enableDragWhenActive: true, _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
+ { backgroundColor: 'transparent', dropAction: 'move', _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
id
);
}
@@ -1122,7 +1111,9 @@ export namespace Docs {
}
export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xMargin: 5, _yMargin: 5, ...options, _type_collection: CollectionViewType.Tree }, id, undefined, protoId);
+ const doc = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xMargin: 5, _yMargin: 5, ...options, _type_collection: CollectionViewType.Tree }, id, undefined, protoId);
+ Doc.GetProto(doc).treeView = ''; /// not really needed, but makes keyvalue pane look better
+ return doc;
}
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
@@ -1155,8 +1146,8 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.LABEL), undefined, { ...(options || {}) });
}
- export function EquationDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.EQUATION), undefined, { ...(options || {}) }, undefined, 'text');
+ export function EquationDocument(text?: string, options?: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.EQUATION), text, { ...(options || {}) }, undefined, 'text');
}
export function FunctionPlotDocument(documents: Array<Doc>, options?: DocumentOptions) {
@@ -1174,20 +1165,19 @@ export namespace Docs {
export function FontIconDocument(options?: DocumentOptions) {
return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
}
- export function FilterDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.FILTER), undefined, { ...(options || {}) });
- }
export function PresElementBoxDocument() {
return Prototypes.get(DocumentType.PRESELEMENT);
}
export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) {
- return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc);
+ return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', type: 'dataviz', ...options }, undefined, undefined, undefined, overwriteDoc);
}
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, _type_collection: CollectionViewType.Docking, dockingConfig: config }, id);
+ const ret = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeView_FreezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, dockingConfig: config }, id);
+ documents.map(c => Doc.SetContainer(c, ret));
+ return ret;
}
export function DirectoryImportDocument(options: DocumentOptions = {}) {
@@ -1212,10 +1202,13 @@ export namespace Docs {
const doc = DockDocument(
configs.map(c => c.doc),
JSON.stringify(layoutConfig),
- options,
+ Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options },
id
);
- configs.map(c => (c.doc.embedContainer = doc));
+ configs.map(c => {
+ Doc.SetContainer(c.doc, doc);
+ inheritParentAcls(doc, c.doc, false);
+ });
return doc;
}
@@ -1244,7 +1237,7 @@ export namespace DocUtils {
const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
childFilters.forEach(filter => {
- const fields = filter.split(':');
+ const fields = filter.split(Doc.FilterSep);
const key = fields[0];
const value = fields[1];
const modifiers = fields[2];
@@ -1262,7 +1255,7 @@ export namespace DocUtils {
return false;
}
- for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(':')[0])) {
+ for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(Doc.FilterSep)[0])) {
const facet = filterFacets[facetKey];
// facets that match some value in the field of the document (e.g. some text field)
@@ -1306,7 +1299,7 @@ export namespace DocUtils {
if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
}
}
- return (parentCollection?.currentFilter as Doc)?.childFilters_boolean === 'OR' ? false : true;
+ return parentCollection?.childFilters_boolean === 'OR' ? false : true;
})
: childDocs;
const rangeFilteredDocs = filteredDocs.filter(d => {
@@ -1375,8 +1368,8 @@ export namespace DocUtils {
source,
target,
{
- 'acl-Public': SharingPermissions.Augment,
- '_acl-Public': SharingPermissions.Augment,
+ 'acl-Guest': SharingPermissions.Augment,
+ '_acl-Guest': SharingPermissions.Augment,
title: ComputedField.MakeFunction('generateLinkTitle(self)') as any,
link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
@@ -1550,7 +1543,7 @@ export namespace DocUtils {
!simpleMenu &&
ContextMenu.Instance.addItem({
description: 'Quick Notes',
- subitems: DocListCast((Doc.UserDoc()['template-notes'] as Doc).data).map((note, i) => ({
+ subitems: DocListCast((Doc.UserDoc()['template_notes'] as Doc).data).map((note, i) => ({
description: ':' + StrCast(note.title),
event: undoable((args: { x: number; y: number }) => {
const textDoc = Docs.Create.TextDocument('', {
@@ -1610,10 +1603,10 @@ export namespace DocUtils {
}
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 clickFuncs = DocListCast(Cast(Doc.UserDoc()['template-clickFuncs'], 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()['template_clickFuncs'], Doc, null)?.data);
const allTemplates = iconViews
.concat(templBtns)
.concat(noteTypes)
@@ -1695,11 +1688,10 @@ export namespace DocUtils {
});
});
if (create) {
- const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2 });
+ const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true });
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;
}
}
@@ -1766,7 +1758,7 @@ export namespace DocUtils {
return;
}
const full = { ...options, _width: 400, title: name };
- const pathname = Utils.prepend(result.accessPaths.agnostic.client);
+ const pathname = result.accessPaths.agnostic.client;
const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc);
if (doc) {
const proto = Doc.GetProto(doc);
@@ -1891,14 +1883,11 @@ export namespace DocUtils {
export function copyDragFactory(dragFactory: Doc) {
if (!dragFactory) return undefined;
const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
- ndoc && Doc.AddFileOrphan(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) {
diff --git a/src/fields/ListSpec.ts b/src/client/theme.ts
index e69de29bb..e69de29bb 100644
--- a/src/fields/ListSpec.ts
+++ b/src/client/theme.ts
diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss
index a5024247e..11e31fe2e 100644
--- a/src/client/util/CaptureManager.scss
+++ b/src/client/util/CaptureManager.scss
@@ -155,21 +155,3 @@
}
}
-.close-button {
- position: absolute;
- top: 10;
- right: 10;
- background:transparent;
- border-radius:100%;
- width: 25px;
- height: 25px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: 0.2s;
-}
-
-.close-button:hover {
- background: rgba(0,0,0,0.1);
-}
-
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index d68761ba7..f42336ee7 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -58,7 +58,6 @@ export class CaptureManager extends React.Component<{}> {
)
);
}
-
return (
<div className="capture-block">
<div className="capture-block-title">Links</div>
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 59041862f..6f530a512 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,4 +1,4 @@
-import { reaction } from "mobx";
+import { observable, reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { FieldLoader } from "../../fields/FieldLoader";
@@ -12,7 +12,7 @@ import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
-import { OmitKeys, Utils } from "../../Utils";
+import { OmitKeys, Utils, addStyleSheetRule } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
@@ -20,7 +20,7 @@ import { TreeViewType } from "../views/collections/CollectionTreeView";
import { DashboardView } from "../views/DashboardView";
import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
-import { ButtonType } from "../views/nodes/button/FontIconBox";
+import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
import { OpenWhere } from "../views/nodes/DocumentView";
import { OverlayView } from "../views/OverlayView";
import { DragManager, dropActionType } from "./DragManager";
@@ -28,8 +28,9 @@ import { MakeTemplate } from "./DropConverter";
import { FollowLinkScript } from "./LinkFollower";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
-import { ColorScheme } from "./SettingsManager";
+import { ColorScheme, SettingsManager } from "./SettingsManager";
import { UndoManager } from "./UndoManager";
+import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
interface Button {
// DocumentOptions fields a button can set
@@ -97,7 +98,7 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
- _stayInCollection: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
+ _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
};
const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
@@ -123,7 +124,7 @@ export class CurrentUserUtils {
}
/// Initializes templates for editing click funcs of a document
- static setupClickEditorTemplates(doc: Doc, field = "template-clickFuncs") {
+ static setupClickEditorTemplates(doc: Doc, field = "template_clickFuncs") {
const tempClicks = DocCast(doc[field]);
const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, isSystem: true};
const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
@@ -144,14 +145,14 @@ export class CurrentUserUtils {
}
/// Initializes templates that can be applied to notes
- static setupNoteTemplates(doc: Doc, field="template-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, layout_autoHeight: true, layout_fitWidth: true};
+ const reqdOpts = {...opts, isSystem:true, title: "text", width:200, layout_autoHeight: true, layout_fitWidth: 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");
});
@@ -162,7 +163,6 @@ export class CurrentUserUtils {
/// Initializes collection of templates for notes and click functions
static setupDocTemplates(doc: Doc, field="myTemplates") {
- DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(), { });
const templates = [
CurrentUserUtils.setupNoteTemplates(doc),
CurrentUserUtils.setupClickEditorTemplates(doc)
@@ -174,7 +174,7 @@ export class CurrentUserUtils {
}
// setup templates for different document types when they are iconified from Document Decorations
- static setupDefaultIconTemplates(doc: Doc, field="template-icons") {
+ static setupDefaultIconTemplates(doc: Doc, field="template_icons") {
const reqdOpts = { title: "icon templates", _height: 75, isSystem: true };
const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
@@ -265,7 +265,7 @@ export class CurrentUserUtils {
}[] = [
{key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }},
{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
- {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, }},
+ {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }},
{key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
{key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }},
{key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }},
@@ -278,12 +278,12 @@ export class CurrentUserUtils {
{key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }},
- {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeViewHideUnrendered: true}},
- {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, targetDropAction: "embed" as dropActionType, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
+ {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+ {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
{key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
{key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree,
- treeViewHasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true,
- allowOverlayDrop: true, treeViewType: TreeViewType.outline,
+ treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true,
+ dropAction:'move', treeView_Type: TreeViewType.outline,
backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true
}, funcs: {title: 'self.text?.Text'}},
];
@@ -294,7 +294,7 @@ export class CurrentUserUtils {
{ toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)},
{ toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)},
{ toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)},
- { toolTip: "Tap or drag to create a physics simulation", title: "Simulation", icon: "atom", dragFactory: doc.emptySimulation as Doc, },
+ { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
{ toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
{ toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)},
@@ -302,18 +302,19 @@ export class CurrentUserUtils {
{ toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay},
{ toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)},
{ toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay},
- { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay},
+ { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)},
- { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript)},
- { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
- { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay},
- { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true },
+ { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
+ { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
{ toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
- { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack>" as any, openFactoryLocation: OpenWhere.overlay},
+ // { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack>" as any, openFactoryLocation: OpenWhere.overlay},
].map(tuple => (
{ openFactoryLocation: OpenWhere.addRight,
scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)',
onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }'},
+ funcs: tuple.funcs,
...tuple, }))
}
@@ -322,61 +323,59 @@ export class CurrentUserUtils {
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,
- _width: 35, _height: 35, _layout_hideContextMenu: true, _stayInCollection: true,
+ _width: 60, _height: 60, _layout_hideContextMenu: true, _dragOnlyWithinContainer: true,
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
- _removeDropProperties: new List<string>(["_stayInCollection"]),
};
return DocUtils.AssignScripts(DocUtils.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs);
});
const reqdOpts:DocumentOptions = {
- title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
+ title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
- childDropAction: 'embed'
+ childDragAction: 'embed'
};
const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
}
/// 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}}[] {
+ static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}, hidden?: boolean}[] {
const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())";
const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails";
return [
- { title: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
- { 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: Doc.UserDoc(), icon: "pres-trail", funcs: {target: getActiveDashTrails}},
- { title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Dashboards", toolTip: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), ignoreClick: true, icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Search", toolTip: "Search ⌘F", target: this.setupSearcher(doc, "mySearcher"), ignoreClick: true, icon: "search", },
+ { title: "Files", toolTip: "Files", target: this.setupFilesystem(doc, "myFilesystem"), ignoreClick: true, icon: "folder-open", },
+ { title: "Tools", toolTip: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), ignoreClick: true, icon: "wrench", },
+ { title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), ignoreClick:false, icon: "upload", },
+ { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), ignoreClick: true, icon: "archive", hidden: true }, // this doc is hidden from the Sidebar, but it's still being used in MyFilesystem which ignores the hidden field
+ { title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, ignoreClick: true, icon: "users", funcs: {badgeValue: badgeValue}},
+ { title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}},
+ { title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), ignoreClick: true, 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.isSystem = true; return doc;})(new Doc()), {isSystem:true});
+ DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as any), {title:"leftSidebarPanel", isSystem:true, undoIgnoreFields: new List<string>(['proto'])});
}
/// 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 menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, hidden, scripts, funcs }) => {
const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
const reqdBtnOpts:DocumentOptions = {
- title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, dontUndo: true, dontRegisterView: true,
- _width: 60, _height: 60, _stayInCollection: true, _layout_hideContextMenu: true,
- _removeDropProperties: new List<string>(["_stayInCollection"]),
+ title, icon, target, toolTip, hidden, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
+ _width: 60, _height: 60, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
};
return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
});
const reqdStackOpts:DocumentOptions ={
- title: "menuItemPanel", childDropAction: "same", backgroundColor: Colors.DARK_GRAY, layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
- _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
+ title: "menuItemPanel", childDragAction: "same", layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
+ _chromeHidden: true, _gridGap: 0, _yMargin: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
@@ -458,7 +457,7 @@ export class CurrentUserUtils {
/// 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", isSystem: true, childDropAction: "embed",
+ dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: "embed",
_lockedPosition: true, _type_collection: CollectionViewType.Schema });
}
@@ -469,7 +468,7 @@ export class CurrentUserUtils {
const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
- _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true,
+ _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
}
@@ -481,7 +480,7 @@ export class CurrentUserUtils {
const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
const newDashboard = `createNewDashboard()`;
- const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true,
+ const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", isSystem: true };
const reqdBtnScript = {onClick: newDashboard,}
const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
@@ -494,9 +493,9 @@ export class CurrentUserUtils {
const childContextMenuLabels = ["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
const reqdOpts:DocumentOptions = {
- title: "My Dashboards", childHideLinkButton: true, treeViewFreezeChildren: "remove|add", treeViewHideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeViewTruncateTitleWidth: 350, ignoreClick: true,
- layout_headerButton: newDashboardButton, childDropAction: "embed",
+ title: "My Dashboards", childHideLinkButton: true, treeView_FreezeChildren: "remove|add", treeView_HideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
+ dropAction: "same", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true,
+ layout_headerButton: newDashboardButton, childDragAction: "none",
_layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
contextMenuLabels:new List<string>(contextMenuLabels),
contextMenuIcons:new List<string>(contextMenuIcons),
@@ -520,25 +519,25 @@ export class CurrentUserUtils {
/// 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, isSystem: true, isFolder: true });
const newFolder = `TreeView_addNewFolder()`;
const newFolderOpts: DocumentOptions = {
- _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _width: 30, _height: 30,
- title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
+ _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
+ title: "New folder", color: Colors.BLACK, btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
};
const newFolderScript = { onClick: newFolder};
const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
- const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true,
- title: "My Documents", layout_headerButton: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", isSystem: true,
- isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "embed",
+ const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true,
+ title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true,
+ isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
+ treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
childContextMenuLabels: new List<string>(["Create new folder"]),
childContextMenuIcons: new List<string>(["plus"]),
layout_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 fileFolders = new Set(DocListCast(DocCast(doc[field])?.data));
+ myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders));
const childContextMenuScripts = [newFolder];
if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) {
myFilesystem.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
@@ -548,9 +547,9 @@ export class CurrentUserUtils {
/// initializes the panel displaying docs that have been recently closed
static setupRecentlyClosed(doc: Doc, field:string) {
- const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true,
- title: "My Recently Closed", childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "embed", isSystem: true,
- treeViewTruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
+ const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, isFolder: true,
+ title: "My Recently Closed", childHideLinkButton: true, treeView_HideTitle: true, childDragAction: "move", isSystem: true,
+ treeView_TruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same",
contextMenuLabels: new List<string>(["Empty recently closed"]),
contextMenuIcons:new List<string>(["trash"]),
layout_explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
@@ -558,13 +557,11 @@ export class CurrentUserUtils {
const recentlyClosed = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
const clearAll = (target:string) => `getProto(${target}).data = new List([])`;
- const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true,
- title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", isSystem: true,
+ const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
+ title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, color: Colors.BLACK, buttonText: "Empty", icon: "trash", isSystem: true,
toolTip: "Empty recently closed",};
DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
- //if (recentlyClosed.layout_headerButton !== clearDocsButton) Doc.GetProto(recentlyClosed).layout_headerButton = clearDocsButton;
-
if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) {
recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
}
@@ -575,10 +572,10 @@ export class CurrentUserUtils {
static setupUserDocView(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
_lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
- layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, isSystem: true,
- treeViewHideTitle: true, treeViewTruncateTitleWidth: 350
+ layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same", ignoreClick: true, isSystem: true,
+ treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
};
- if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
+ if (!doc[field]) DocUtils.AssignOpts(doc, {treeView_Open: true, treeView_ExpandedView: "fields" });
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]);
}
@@ -587,10 +584,15 @@ export class CurrentUserUtils {
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
_lockedPosition: true, isSystem: true, flexDirection: "row"
})
+ static multiToggleList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.FontIconDocument({
+ ...opts, data:docs, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true,
+ dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
+ _lockedPosition: true, isSystem: true, flexDirection: "row"
+ })
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true,
- _removeDropProperties: new List<string>([ "_layout_hideContextMenu", "stayInCollection"]),
+ _dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]),
_nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts,
})
@@ -602,19 +604,19 @@ export class CurrentUserUtils {
CurrentUserUtils.createToolButton(opts), scripts, funcs);
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" }},
+ { scripts: { onClick: "undo()"}, opts: { title: "Undo", icon: "undo-alt", toolTip: "Undo ⌘Z" }},
+ { scripts: { onClick: "redo()"}, opts: { title: "Redo", icon: "redo-alt", toolTip: "Redo ⌘⇧Z" }},
{ scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet)
{ scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}},
{ scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
];
- const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
+ const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts));
const dockBtnsReqdOpts:DocumentOptions = {
- title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDropAction: 'embed',
- childDontRegisterViews: true, linearView_IsExpanded: true, linearView_Expandable: true, ignoreClick: true
+ title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
+ childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: 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 });
+ 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);
}
@@ -627,30 +629,34 @@ export class CurrentUserUtils {
}
static viewTools(): Button[] {
return [
- { title: "Snap\xA0Lines", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "View\xA0All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
return [
{ title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
- { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, width: 75, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0 },
+ { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
{ title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
- { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
- { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
- { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
+ { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Align", toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true,
+ subMenu: [
+ { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
+ { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ ]
+ },
+ { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
{ title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
// { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
@@ -695,19 +701,20 @@ export class CurrentUserUtils {
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: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 20, scripts: { onClick: 'pinWithView(altKey)'}},
- { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
- { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}},
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
- { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
- { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
- { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
- { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
- { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
+ { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
+ { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
+ { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+ { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Num", icon:"", toolTip: "Frame Number (click to toggle edit mode)", btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
+ { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+ { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+ { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+ { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+ { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+ { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
+ { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
+ { title: "Audio", icon: "microphone", toolTip: "Dictate", btnType: ButtonType.ToggleButton, expertMode: false, ignoreClick: true, scripts: { onClick: 'return toggleRecording(_readOnly_)'}, funcs: { }}
];
}
@@ -715,40 +722,42 @@ export class CurrentUserUtils {
static setupContextMenuButton(params:Button, btnDoc?:Doc) {
const reqdOpts:DocumentOptions = {
...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
- backgroundColor: params.backgroundColor ??"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, isSystem: true, //dontUndo: true,
+ color: Colors.WHITE, isSystem: true,
_nativeWidth: params.width ?? 30, _width: params.width ?? 30,
_height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
toolType: params.toolType, expertMode: params.expertMode,
- _stayInCollection: true, _layout_hideContextMenu: true, _lockedPosition: true,
- _removeDropProperties: new List<string>([ "_stayInCollection"]),
+ _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _lockedPosition: true,
};
const reqdFuncs:{[key:string]:any} = {
...params.funcs,
- backgroundColor: params.btnType === ButtonType.ToggleButton ? params.scripts?.onClick:undefined /// 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);
}
+ static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
+ const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title);
+ const subMenu = params.subMenu;
+ if (!subMenu) { // button does not have a sub menu
+ return this.setupContextMenuButton(params, menuBtnDoc);
+ }
+ // linear view
+ const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
+ childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
+ linearView_SubMenu: true, linearView_Expandable: params.btnType !== ButtonType.MultiToggleButton};
+
+ const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+ const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
+ const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title),
+ (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
+ if (!menuBtnDoc) Doc.GetProto(btnDoc).data = new List<Doc>(items(btnDoc));
+ return btnDoc;
+ }
+
/// Initializes all the default buttons for the top bar context menu
static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
- const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", dontUndo:true, flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true, linearView_IsExpanded: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+ const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: 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, dontUndo: true,
- childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
- linearView_SubMenu: true, linearView_Expandable: 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), params.scripts, params.funcs);
- }
- });
+ const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
}
@@ -767,8 +776,9 @@ export class CurrentUserUtils {
const linkDocs = new Doc(linkDatabaseId, true);
linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
linkDocs.author = Doc.CurrentUserEmail;
+ linkDocs.isSystem = true;
linkDocs.data = new List<Doc>([]);
- linkDocs["acl-Public"] = SharingPermissions.Augment;
+ linkDocs["acl-Guest"] = SharingPermissions.Augment;
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
}
@@ -778,41 +788,40 @@ export class CurrentUserUtils {
// 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._type_collection === '${CollectionViewType.Docking}'`, { doc: Doc.name });
const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}";
- const sharedScripts = { treeViewChildDoubleClick: dblClkScript, }
+ const sharedScripts = { treeView_ChildDoubleClick: dblClkScript, }
const sharedDocOpts:DocumentOptions = {
title: "My Shared Docs",
userColor: "rgb(202, 202, 202)",
- isFolder:true,
- 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: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
- // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
- _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
+ isFolder:true, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
+ // childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]),
+ // childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
+ // childContextMenuLabels: new List<string>(["Add to Dashboards",]),
+ // childContextMenuIcons: new List<string>(["user-plus",]),
+ "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment,
+ childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
+ // NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
+ _layout_showTitle: "title", treeView_HideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
layout_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);
+ if (!Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards) Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards = new List<Doc>();
}
/// Import option on the left side button panel
- static setupImportSidebar(doc: Doc, field:string) {
+ static setupImportSidebar(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
- title: "My Imports", _forceActive: true, ignoreClick: true, _layout_showTitle: "title",
- _stayInCollection: true, _layout_hideContextMenu: true, childLimitHeight: 0,
- childDropAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
+ title: "My Imports", _forceActive: true, _layout_showTitle: "title", childLayoutString: ImportElementBox.LayoutString('data'),
+ _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, childLimitHeight: 0, onClickScriptDisable:"never",
+ childDragAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
dontRegisterView: true, layout_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 myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.MasonryDocument([], opts), reqdOpts, undefined, {onClick: "deselectAll()"});
const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer",
- _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
+ _width: 30, _height: 30, color: Colors.BLACK, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
buttonText: "Import", icon: "upload", isSystem: true };
DocUtils.AssignDocField(myImports, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
return myImports;
@@ -826,7 +835,7 @@ export class CurrentUserUtils {
async () => {
const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
+ SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]);
}, { fireImmediately: true });
doc.isSystem ?? (doc.isSystem = true);
doc.title ?? (doc.title = Doc.CurrentUserEmail);
@@ -850,8 +859,14 @@ export class CurrentUserUtils {
doc.fontHighlight ?? (doc.fontHighlight = "");
doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false);
doc.savedFilters ?? (doc.savedFilters = new List<Doc>());
+ doc.userBackgroundColor ?? (doc.userBackgroundColor = Colors.DARK_GRAY);
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${doc.userBackgroundColor} !important` });
+ doc.userVariantColor ?? (doc.userVariantColor = Colors.MEDIUM_BLUE);
+ doc.userColor ?? (doc.userColor = Colors.LIGHT_GRAY);
+ doc.userTheme ?? (doc.userTheme = ColorScheme.Dark);
doc.filterDocCount = 0;
- doc.treeViewFreezeChildren = "remove|add";
+ doc.treeView_FreezeChildren = "remove|add";
+ doc.activePage = doc.activeDashboard === undefined ? 'home': doc.activePage;
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
@@ -862,11 +877,13 @@ export class CurrentUserUtils {
this.setupDockedButtons(doc); // the bottom bar of font icons
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
+ //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", isSystem: true }); // drop down panel at top of dashboard for stashing documents
+ DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed)
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)
@@ -874,7 +891,8 @@ export class CurrentUserUtils {
}
new LinkManager();
- setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
+ DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
+ setInterval(DocServer.UPDATE_SERVER_CACHE, 120000);
return doc;
}
static setupFieldInfos(doc:Doc, field="fieldInfos") {
@@ -896,22 +914,22 @@ export class CurrentUserUtils {
});
}
+ @observable public static ServerVersion: string = ';'
public static async loadCurrentUser() {
return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
if (response) {
- const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response);
+ const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response);
+ runInAction(() => CurrentUserUtils.ServerVersion = result.version);
Doc.CurrentUserEmail = result.email;
- resolvedPorts = JSON.parse(await (await fetch("/resolvedPorts")).text());
+ resolvedPorts = result.resolvedPorts as any;
DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email);
if (result.cacheDocumentIds)
{
const ids = result.cacheDocumentIds.split(";");
const batch = 30000;
- FieldLoader.active = true;
for (let i = 0; i < ids.length; i = Math.min(ids.length, i+batch)) {
await DocServer.GetRefFields(ids.slice(i, i+batch));
}
- FieldLoader.active = false;
}
return result;
} else {
@@ -920,27 +938,24 @@ export class CurrentUserUtils {
});
}
- public static async loadUserDocument(id: string) {
- await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
- const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
- 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;
- 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?");
+ public static async loadUserDocument(info:{
+ userDocumentId: string;
+ sharingDocumentId: string;
+ linkDatabaseId: string;
+ }) {
+ return DocServer.GetRefField(info.userDocumentId).then(async field => {
+ Docs.newAccount = !(field instanceof Doc);
+ await Docs.Prototypes.initialize();
+ const userDoc = Docs.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
+ this.updateUserDocument(Doc.SetUserDoc(userDoc), info.sharingDocumentId, info.linkDatabaseId);
+ if (Docs.newAccount) {
+ if (Doc.CurrentUserEmail === "guest") {
+ DashboardView.createNewDashboard(undefined, "guest dashboard");
+ } else {
+ userDoc.activePage = "home";
+ }
}
+ return userDoc;
});
}
@@ -951,10 +966,8 @@ export class CurrentUserUtils {
input.multiple = true;
input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
input.onchange = async _e => {
- const upload = Utils.prepend("/uploadDoc");
- const formData = new FormData();
const file = input.files?.[0];
- if (file?.type === 'application/zip') {
+ if (file?.type === 'application/zip' || file?.type === 'application/x-zip-compressed') {
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 =>
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 612f16ce9..7c3b5be05 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,10 +1,11 @@
import { action, computed, observable, ObservableSet } from 'mobx';
-import { Doc, Opt } from '../../fields/Doc';
-import { Animation } from '../../fields/DocSymbols';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
+import { GetEffectiveAcl } from '../../fields/util';
import { CollectionViewType } from '../documents/DocumentTypes';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { TabDocView } from '../views/collections/TabDocView';
@@ -25,7 +26,13 @@ export class DocumentManager {
@observable public RecordingEvent = 0;
@observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
@computed public get DocumentViews() {
- return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox));
+ return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath)));
+ }
+ public AddDocumentView(dv: DocumentView) {
+ this._documentViews.add(dv);
+ }
+ public DeleteDocumentView(dv: DocumentView) {
+ this._documentViews.delete(dv);
}
private static _instance: DocumentManager;
@@ -68,7 +75,7 @@ export class DocumentManager {
@action
public AddView = (view: DocumentView) => {
- //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
+ if (view.props.LayoutTemplateString?.includes(KeyValueBox.name)) return;
if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const viewAnchorIndex = view.props.LayoutTemplateString.includes('link_anchor_2') ? 'link_anchor_2' : 'link_anchor_1';
const link = view.rootDoc;
@@ -83,7 +90,7 @@ export class DocumentManager {
// 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.add(view);
+ this.AddDocumentView(view);
}
this.callAddViewFuncs(view);
};
@@ -101,7 +108,7 @@ export class DocumentManager {
const index = this.LinkAnchorBoxViews.indexOf(view);
this.LinkAnchorBoxViews.splice(index, 1);
} else {
- this._documentViews.delete(view);
+ this.DeleteDocumentView(view);
}
SelectionManager.DeselectView(view);
});
@@ -188,7 +195,7 @@ export class DocumentManager {
var containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
- containerDocContext[0]?.embedContainer &&
+ DocCast(containerDocContext[0]?.embedContainer) &&
DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking &&
(includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0]))
) {
@@ -198,7 +205,7 @@ export class DocumentManager {
}
static playAudioAnno(doc: Doc) {
- const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement();
+ const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
if (anno instanceof AudioField) {
new Howl({
@@ -228,7 +235,7 @@ export class DocumentManager {
public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
const docViewPath = targetDocView.docViewPath.slice();
let rootContextView = docViewPath.shift();
- await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
+ await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
};
@@ -242,20 +249,23 @@ export class DocumentManager {
public showDocument = async (
targetDoc: Doc, // document to display
options: DocFocusOptions, // options for how to navigate to target
- finished?: () => void
+ finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
+ Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
- let rootContextView = await new Promise<DocumentView>(res => {
- const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
- if (viewIndex !== -1) {
- viewIndex && docContextPath.splice(0, viewIndex);
- return res(this.getDocumentView(docContextPath[0])!);
- }
- options.didMove = true;
- docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
- this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
- });
+ let rootContextView =
+ docContextPath.length &&
+ (await new Promise<DocumentView>(res => {
+ const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
+ if (viewIndex !== -1) {
+ viewIndex && docContextPath.splice(0, viewIndex);
+ return res(this.getDocumentView(docContextPath[0])!);
+ }
+ options.didMove = true;
+ docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
+ }));
if (options.openLocation === OpenWhere.lightbox) {
// even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
const target = DocCast(targetDoc.annotationOn, targetDoc);
@@ -267,21 +277,29 @@ export class DocumentManager {
docContextPath.shift();
const childViewIterator = async (docView: DocumentView) => {
const innerDoc = docContextPath.shift();
- return { viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
+ return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
};
- const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
- this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
- finished?.();
+ if (rootContextView) {
+ const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+ finished?.(target.focused);
+ } else finished?.(false);
};
- focusViewsInPath = async (docView: DocumentView, options: DocFocusOptions, iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView> }>) => {
+ focusViewsInPath = async (
+ docView: DocumentView, //
+ options: DocFocusOptions,
+ iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
+ ) => {
let contextView: DocumentView | undefined; // view containing context that contains target
+ let focused = false;
while (true) {
docView.rootDoc.layout_fieldKey === 'layout_icon' ? await new Promise<void>(res => docView.iconify(res)) : undefined;
- docView.props.focus(docView.rootDoc, options); // focus the view within its container
+ const nextFocus = docView.props.focus(docView.rootDoc, options); // focus the view within its container
+ focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView };
+ if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView, focused };
contextView = docView;
docView = childDocView;
}
@@ -297,10 +315,10 @@ export class DocumentManager {
if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden;
if (options.effect) docView.rootDoc[Animation] = options.effect;
- if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.textHtml) {
+ if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.text_html) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
- contextView.htmlOverlayEffect = StrCast(options?.effect?.presEffect, StrCast(options?.effect?.followLinkAnimEffect));
- contextView.textHtmlOverlay = StrCast(targetDoc.textHtml);
+ contextView.htmlOverlayEffect = StrCast(options?.effect?.presentation_effect, StrCast(options?.effect?.followLinkAnimEffect));
+ contextView.textHtmlOverlay = StrCast(targetDoc.text_html);
DocumentManager._overlayViews.add(contextView);
}
Doc.AddUnHighlightWatcher(() => {
@@ -318,7 +336,7 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
} else {
- const container = DocCast(containingDoc ?? doc.embedContainer ?? doc);
+ const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
const showDoc = !Doc.IsSystem(container) ? container : doc;
options.toggleTarget = undefined;
DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
@@ -328,6 +346,9 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
});
}
};
+ if (Doc.IsDataProto(doc) && DocListCast(doc.proto_embeddings).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
+ doc = DocListCast(doc.proto_embeddings).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
+ }
if (doc.hidden) {
doc.hidden = false;
options.toggleTarget = false;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 668de5408..f4ff38515 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -15,42 +15,36 @@ import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-export type dropActionType = 'embed' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call removeDropProperties
+export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
/**
* Initialize drag
* @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> | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, dragStarted?: () => void) {
- const onRowMove = async (e: PointerEvent) => {
+export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | undefined) {
+ const onRowMove = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
- const doc = await docFunc();
+ const doc = docFunc();
if (doc) {
const dragData = new DragManager.DocumentDragData([doc]);
- dragData.dropAction = dropAction;
- dragData.moveDocument = moveFunc;
DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
- dragStarted?.();
}
};
const onRowUp = (): void => {
document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
};
- const onItemDown = async (e: React.PointerEvent) => {
+ const onItemDown = (e: React.PointerEvent) => {
if (e.button === 0) {
e.stopPropagation();
if (e.shiftKey) {
e.persist();
- const dragDoc = await docFunc();
+ const dragDoc = docFunc();
dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]);
} else {
document.addEventListener('pointermove', onRowMove);
@@ -132,7 +126,7 @@ export namespace DragManager {
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 'embed', but the document is dropped within the same collection, the drop action will be switched to 'move'
- removeDropProperties?: string[];
+ dropPropertiesToRemove?: string[];
moveDocument?: MoveFunction;
removeDocument?: RemoveFunction;
isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
@@ -189,7 +183,7 @@ export namespace DragManager {
const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
const preDropHandler = (e: Event) => {
const de = (e as CustomEvent<DropEvent>).detail;
- (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.targetDropAction) as dropActionType);
+ (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.dropAction) as dropActionType);
};
element.addEventListener('dashOnDrop', handler);
doc && element.addEventListener('dashPreDrop', preDropHandler);
@@ -219,6 +213,8 @@ export namespace DragManager {
? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
: docDragData.dropAction === 'embed'
? Doc.BestEmbedding(d)
+ : docDragData.dropAction === 'add'
+ ? d
: docDragData.dropAction === 'proto'
? Doc.GetProto(d)
: docDragData.dropAction === 'copy'
@@ -230,11 +226,13 @@ export namespace DragManager {
)
).filter(d => d);
!['same', 'proto'].includes(docDragData.dropAction as any) &&
- docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
- const dragProps = StrListCast(dragData.draggedDocuments[i].removeDropProperties);
- const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
- remProps.map(prop => (drop[prop] = undefined));
- });
+ docDragData.droppedDocuments
+ // .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any))
+ .forEach((drop: Doc, i: number) => {
+ const dragProps = StrListCast(dragData.draggedDocuments[i].dropPropertiesToRemove);
+ const remProps = (dragData?.dropPropertiesToRemove || []).concat(Array.from(dragProps));
+ [...remProps, 'dropPropertiesToRemove'].map(prop => (drop[prop] = undefined));
+ });
}
return e;
};
@@ -586,6 +584,7 @@ export namespace DragManager {
async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
const dropArgs = {
+ cancelable: true, // allows preventDefault() to be called to cancel the drop
bubbles: true,
detail: {
...pos,
@@ -598,8 +597,9 @@ export namespace DragManager {
},
};
target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
+ UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
await finishDrag?.(complete);
- target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs));
+ UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
options?.dragComplete?.(complete);
endDrag?.();
}
@@ -614,8 +614,8 @@ ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?:
? 'transparent'
: DragManager.GetRaiseWhenDragged()
? Colors.MEDIUM_BLUE_ALT
- : Colors.PINK;
- return DragManager.GetRaiseWhenDragged() ? Colors.PINK : 'transparent';
+ : Colors.LIGHT_BLUE;
+ return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE_ALT : 'transparent';
}
if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false)));
else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged());
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f46ea393a..f235be192 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -9,7 +9,7 @@ import { RichTextField } from '../../fields/RichTextField';
import { ImageField } from '../../fields/URLField';
import { ScriptingGlobals } from './ScriptingGlobals';
import { listSpec } from '../../fields/Schema';
-import { ButtonType } from '../views/nodes/button/FontIconBox';
+import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox';
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
@@ -57,11 +57,11 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
let dbox = doc;
// bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes('FontIconBox')) {
- if (data.removeDropProperties || dbox.removeDropProperties) {
+ if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
//dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
dbox = Doc.MakeEmbedding(dbox);
- const dragProps = Cast(dbox.removeDropProperties, listSpec('string'), []);
- const remProps = (data.removeDropProperties || []).concat(Array.from(dragProps));
+ const dragProps = Cast(dbox.dropPropertiesToRemove, listSpec('string'), []);
+ const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
remProps.map(prop => (dbox[prop] = undefined));
}
} else if (!doc.onDragStart && !doc.isButtonBar) {
@@ -81,7 +81,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
icon: layoutDoc.isTemplateDoc ? 'font' : 'bolt',
});
dbox.dragFactory = layoutDoc;
- dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
+ dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
} else if (doc.isButtonBar) {
dbox.ignoreClick = true;
diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 9438bdd72..673af16ee 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,38 +1,54 @@
.group-interface {
width: 380px;
height: 300px;
+ position: relative;
.dialogue-box {
.group-create {
display: flex;
flex-direction: column;
- height: 90%;
+ overflow: visible;
+ height: 100%;
+ width: 98.5%;
+ padding: 5px;
justify-content: space-between;
margin-left: 5px;
- input {
- border-radius: 5px;
- padding: 8px;
- min-width: 100%;
- border: 1px solid hsl(0, 0%, 80%);
- outline: none;
- height: 30;
-
- &:focus {
- border: 2.5px solid #2684FF;
- }
- }
-
p {
font-size: 20px;
text-align: left;
- color: black;
}
+ }
- button {
- align-self: flex-end;
+ .group-input {
+
+ input {
+ padding: 8px;
+ width: 100%;
+ outline: none;
+ border: none;
+ min-width: 100%;
+ border: 2px solid;
+ border-radius: 4px;
+ border-color: inherit;
+ background: white;
+ color: black;
+ height: 30;
}
}
+
+ .select-users {
+ margin-right: 3;
+ max-height: 30;
+ width: 100%;
+ display: inline-flex;
+ flex-direction: row;
+ border: 2px solid;
+ border-radius: 4px;
+ border-color: inherit;
+ background: white;
+ color: black;
+ }
}
@@ -54,10 +70,14 @@
.group-interface {
display: flex;
flex-direction: column;
+ overflow: hidden;
+ padding: 10px;
.overlay {
- transform: translate(-20px, -20px);
- border-radius: 10px;
+ transform: translate(-10px, -10px);
+ width: 400px;
+ height: 320px;
+ overflow: hidden;
}
.delete-button {
@@ -66,10 +86,8 @@
.close-button {
position: absolute;
- right: 1em;
- top: 1em;
- cursor: pointer;
- z-index: 999;
+ right: 2px;
+ top: 2px;
}
.group-heading {
@@ -81,7 +99,6 @@
font-size: 20px;
text-align: left;
margin-right: 15px;
- color: black;
}
}
@@ -94,18 +111,24 @@
margin-left: 5;
width: 50px;
cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ }
+
+ .style-divider {
+ width: 100%;
+ height: 1px;
}
.group-body {
justify-content: space-between;
- height: 220;
- background-color: #e8e8e8;
+ height: 225;
padding-right: 1em;
justify-content: space-around;
text-align: left;
- overflow-y: auto;
+ overflow-y: hidden;
width: 100%;
.group-row {
@@ -117,7 +140,6 @@
.group-name {
max-width: 65%;
margin: 0 10;
- color: black;
}
.group-info {
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index da947aba6..f35844020 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -15,6 +15,8 @@ import { SharingManager, User } from './SharingManager';
import { listSpec } from '../../fields/Schema';
import { DateField } from '../../fields/DateField';
import { Id } from '../../fields/FieldSymbols';
+import { Button, IconButton, Size, Type } from 'browndash-components';
+import { SettingsManager } from './SettingsManager';
/**
* Interface for options for the react-select component
@@ -280,47 +282,59 @@ export class GroupManager extends React.Component<{}> {
*/
private get groupCreationModal() {
const contents = (
- <div className="group-create">
+ <div className="group-create" style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}>
<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'} />
+ <div className="close-button">
+ <Button
+ icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
+ onClick={action(() => {
+ this.createGroupModalOpen = false;
+ TaskCompletionBox.taskCompleted = false;
+ })}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
</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'))} />
- <Select
- isMulti
- options={this.options}
- onChange={this.handleChange}
- 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,
- }),
- multiValue: base => ({
- ...base,
- maxWidth: '50%',
-
- '&:hover': {
- maxWidth: 'unset',
- },
- }),
- }}
- />
- <button ref={this.createGroupButtonRef} onClick={this.createGroup} style={{ background: this.buttonColour }} disabled={this.buttonColour === '#979797'}>
- Create
- </button>
+ <div className="group-input" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
+ <input ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} />
+ </div>
+ <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
+ <Select
+ className="select-users"
+ isMulti
+ options={this.options}
+ onChange={this.handleChange}
+ placeholder={'Select users'}
+ value={this.selectedUsers}
+ closeMenuOnSelect={false}
+ styles={{
+ control: () => ({
+ display: 'inline-flex',
+ width: '100%',
+ }),
+ indicatorSeparator: () => ({
+ display: 'inline-flex',
+ visibility: 'hidden',
+ }),
+ indicatorsContainer: () => ({
+ display: 'inline-flex',
+ textDecorationColor: 'black',
+ }),
+ valueContainer: () => ({
+ display: 'inline-flex',
+ fontStyle: StrCast(Doc.UserDoc().userColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ width: '100%',
+ }),
+ }}
+ />
+ </div>
+ <div className={'create-button'}>
+ <Button text={'Create'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
+ </div>
</div>
);
@@ -353,37 +367,30 @@ export class GroupManager extends React.Component<{}> {
const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
return (
- <div className="group-interface">
+ <div className="group-interface" style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}>
{this.groupCreationModal}
{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
- </button>
- <div className={'close-button'} onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
+ <Button icon={<FontAwesomeIcon icon={'plus'} />} iconPlacement={'left'} text={'Create Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.createGroupModalOpen = true))} />
+ <div className={'close-button'}>
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
</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'} />
- )}
+ Name
+ <IconButton icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} />
</div>
- <div className="group-body">
+ <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+ <div className="group-body" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
{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' }} />
+ <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.currentGroup = group))} />
</div>
</div>
))}
diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss
index 2eb164988..d50569b26 100644
--- a/src/client/util/GroupMemberView.scss
+++ b/src/client/util/GroupMemberView.scss
@@ -1,6 +1,9 @@
.editing-interface {
width: 100%;
height: 100%;
+ padding: 10px;
+ float: right;
+ overflow: visible;
hr {
margin-top: 20;
@@ -10,7 +13,6 @@
outline: none;
border-radius: 5px;
border: 0px;
- color: #fcfbf7;
text-transform: none;
letter-spacing: 2px;
font-size: 75%;
@@ -37,8 +39,6 @@
text-align: center;
border: none;
outline: none;
- color: black;
- margin-top: -5;
height: 20;
text-overflow: ellipsis;
background: none;
@@ -61,21 +61,36 @@
margin-bottom: 25;
.add-member-dropdown {
- width: 65%;
- margin: 0 5;
+ margin-right: 3;
+ max-height: 30;
+ width: 250px;
+ display: inline-flex;
+ flex-direction: row;
+ border: 2px solid;
+ border-radius: 4px;
+ border-color: inherit;
+ background: white;
+ color: black;
+ }
- input {
- height: 30;
- }
+ .delete-button {
+ align-self: center;
+ background: inherit;
}
}
}
+ .style-divider {
+ width: 98%;
+ height: 1px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }
+
.editing-contents {
overflow-y: auto;
height: 62%;
width: 100%;
- color: black;
margin-top: -15px;
.editing-row {
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index b7f89794d..535d8ccc2 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,13 +1,15 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import Select from "react-select";
-import { Doc } from "../../fields/Doc";
-import { StrCast } from "../../fields/Types";
-import { MainViewModal } from "../views/MainViewModal";
-import { GroupManager, UserOptions } from "./GroupManager";
-import "./GroupMemberView.scss";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import Select from 'react-select';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
+import { MainViewModal } from '../views/MainViewModal';
+import { GroupManager, UserOptions } from './GroupManager';
+import './GroupMemberView.scss';
+import { Button, IconButton, Size, Type } from 'browndash-components';
+import { SettingsManager } from './SettingsManager';
interface GroupMemberViewProps {
group: Doc;
@@ -16,93 +18,87 @@ interface GroupMemberViewProps {
@observer
export class GroupMemberView extends React.Component<GroupMemberViewProps> {
-
- @observable private memberSort: "ascending" | "descending" | "none" = "none";
+ @observable private memberSort: 'ascending' | 'descending' | 'none' = 'none';
private get editingInterface() {
let members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
- members = this.memberSort === "ascending" ? members.sort() : this.memberSort === "descending" ? members.sort().reverse() : members;
+ members = this.memberSort === 'ascending' ? members.sort() : this.memberSort === 'descending' ? members.sort().reverse() : members;
const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
const hasEditAccess = GroupManager.Instance.hasEditAccess(this.props.group);
- return (!this.props.group ? null :
- <div className="editing-interface">
+ return !this.props.group ? null : (
+ <div className="editing-interface" style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}>
<div className="editing-header">
<input
className="group-title"
- style={{ marginLeft: !hasEditAccess ? "-14%" : 0 }}
+ style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
value={StrCast(this.props.group.title || this.props.group.groupName)}
- onChange={e => this.props.group.title = e.currentTarget.value}
- disabled={!hasEditAccess}
- >
- </input>
- <div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}>
- <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
+ onChange={e => (this.props.group.title = e.currentTarget.value)}
+ disabled={!hasEditAccess}></input>
+ <div className={'memberView-closeButton'}>
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
</div>
- {GroupManager.Instance.hasEditAccess(this.props.group) ?
+ {GroupManager.Instance.hasEditAccess(this.props.group) ? (
<div className="group-buttons">
- <div className="add-member-dropdown">
+ <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
+ className="add-member-dropdown"
isSearchable={true}
options={options}
onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.props.group, (selectedOption as UserOptions).value)}
- placeholder={"Add members"}
+ placeholder={'Add members'}
value={null}
styles={{
- dropdownIndicator: (base, state) => ({
- ...base,
- transition: '0.5s all ease',
- transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
- })
+ control: () => ({
+ display: 'inline-flex',
+ width: '100%',
+ }),
+ indicatorSeparator: () => ({
+ display: 'inline-flex',
+ visibility: 'hidden',
+ }),
+ indicatorsContainer: () => ({
+ display: 'inline-flex',
+ textDecorationColor: 'black',
+ }),
+ valueContainer: () => ({
+ display: 'inline-flex',
+ fontStyle: StrCast(Doc.UserDoc().userColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ width: '100%',
+ }),
}}
/>
</div>
- <button onClick={() => GroupManager.Instance.deleteGroup(this.props.group)}>Delete group</button>
- </div> :
- null}
- <div
- className="sort-emails"
- style={{ paddingTop: hasEditAccess ? 0 : 35 }}
- onClick={action(() => this.memberSort = this.memberSort === "ascending" ? "descending" : this.memberSort === "descending" ? "none" : "ascending")}>
- Emails {this.memberSort === "ascending" ? "↑" : this.memberSort === "descending" ? "↓" : ""} {/* → */}
+ <div className={'delete-button'}>
+ <Button text={'Delete Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.deleteGroup(this.props.group)} />
+ </div>
+ </div>
+ ) : null}
+ <div className="sort-emails" style={{ paddingTop: hasEditAccess ? 0 : 35 }} onClick={action(() => (this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'))}>
+ Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */}
</div>
</div>
- <hr />
- <div className="editing-contents"
- style={{ height: hasEditAccess ? "62%" : "85%" }}
- >
+ <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+ <div className="editing-contents" style={{ height: hasEditAccess ? '62%' : '85%' }}>
{members.map(member => (
- <div
- className="editing-row"
- key={member}
- >
- <div className="user-email">
- {member}
- </div>
- {hasEditAccess ?
- <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
- <FontAwesomeIcon icon={"trash-alt"} size={"sm"} />
+ <div className="editing-row" key={member}>
+ <div className="user-email">{member}</div>
+ {hasEditAccess ? (
+ <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
+ <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)} />
</div>
- : null}
+ ) : null}
</div>
))}
</div>
</div>
);
-
}
render() {
- return <MainViewModal
- isDisplayed={true}
- interactive={true}
- contents={this.editingInterface}
- dialogueBoxStyle={{ width: 400, height: 250 }}
- closeOnExternalClick={this.props.onCloseButtonClick}
- />;
+ return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
}
-
-
-} \ No newline at end of file
+}
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 043f0f1f3..d0f459291 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -178,7 +178,7 @@ export namespace InteractionUtils {
filter: mask ? `url(#mask${defGuid})` : undefined,
opacity: 1.0,
// opacity: strokeWidth !== width ? 0.5 : undefined,
- pointerEvents: pevents as any,
+ pointerEvents: (pevents as any) === 'all' ? 'visiblepainted' : (pevents as any),
stroke: color ?? 'rgb(0, 0, 0)',
strokeWidth: strokeWidth,
strokeLinecap: lineCap as any,
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index f74409e42..b8fea340f 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -30,7 +30,7 @@ export class LinkFollower {
public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, altKey: boolean) => {
const batch = UndoManager.StartBatch('Follow Link');
runInAction(() => (LinkFollower.IsFollowing = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value
- LinkFollower.traverseLink(
+ return LinkFollower.traverseLink(
linkDoc,
sourceDoc,
action(() => {
@@ -54,7 +54,10 @@ export class LinkFollower {
const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
var count = 0;
const allFinished = () => ++count === followLinks.length && finished?.();
- if (!followLinks.length) finished?.();
+ if (!followLinks.length) {
+ finished?.();
+ return false;
+ }
followLinks.forEach(async linkDoc => {
const target = (
sourceDoc === linkDoc.link_anchor_1
@@ -103,7 +106,7 @@ export class LinkFollower {
Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target);
movedTarget = true;
}
- target.embedContainer = sourceDocParent;
+ Doc.SetContainer(target, sourceDocParent);
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
target.x = moveTo[0];
@@ -120,17 +123,18 @@ export class LinkFollower {
allFinished();
}
});
+ return true;
}
}
ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
SelectionManager.DeselectAll();
- LinkFollower.FollowLink(undefined, doc, altKey);
+ return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true };
});
export function FollowLinkScript() {
- return ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' });
+ return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
}
export function IsFollowLinkScript(field: FieldResult<Field>) {
- return ScriptCast(field)?.script.originalScript.includes('followLink(');
+ return ScriptCast(field)?.script.originalScript.includes('return followLink(');
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index dbb05917e..ef4b21b05 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,10 +1,12 @@
-import { action, observable, observe } from 'mobx';
+import { action, observable, observe, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
import { DirectLinks } from '../../fields/DocSymbols';
+import { FieldLoader } from '../../fields/FieldLoader';
import { List } from '../../fields/List';
import { ProxyField } from '../../fields/Proxy';
import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
+import { DocServer } from '../DocServer';
import { ScriptingGlobals } from './ScriptingGlobals';
/*
* link doc:
@@ -45,38 +47,41 @@ export class LinkManager {
LinkManager._instance = this;
this.createlink_relationshipLists();
LinkManager.userLinkDBs = [];
- const addLinkToDoc = (link: Doc) => {
- Promise.all([link]).then(linkdoc => {
- const link = DocCast(linkdoc[0]);
- Promise.all([link.proto]).then(linkproto => {
- Promise.all([link.link_anchor_1, link.link_anchor_2]).then(linkdata => {
- const a1 = DocCast(linkdata[0]);
- const a2 = DocCast(linkdata[1]);
- a1 &&
- a2 &&
- Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then(
- action(protos => {
- (protos[0] as Doc)?.[DirectLinks].add(link);
- (protos[1] as Doc)?.[DirectLinks].add(link);
+ // since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
+ // Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
+ // Then add the link to the anchor protos.
+ const addLinkToDoc = (lprom: Doc) =>
+ PromiseValue(lprom).then((link: Opt<Doc>) =>
+ PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) =>
+ Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
+ Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
+ Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
+ action(lAnchProtoProtos => {
+ link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
+ link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
})
- );
- });
- });
- });
- };
- const remLinkFromDoc = (link: Doc) => {
- const a1 = link?.link_anchor_1;
- const a2 = link?.link_anchor_2;
- 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)[DirectLinks].delete(link);
- Doc.GetProto(a2)[DirectLinks].delete(link);
- Doc.GetProto(link)[DirectLinks].delete(link);
- }
- })
+ )
+ )
+ )
+ )
);
- };
+
+ const remLinkFromDoc = (lprom: Doc) =>
+ PromiseValue(lprom).then((link: Opt<Doc>) =>
+ PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) =>
+ Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
+ Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
+ Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
+ action(lAnchProtoProtos => {
+ link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].delete(link);
+ link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].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
@@ -128,6 +133,7 @@ export class LinkManager {
},
true
);
+ runInAction(() => (FieldLoader.ServerLoadStatus.message = 'links'));
LinkManager.addLinkDB(Doc.LinkDBDoc());
}
@@ -141,6 +147,7 @@ export class LinkManager {
public addLink(linkDoc: Doc, checkExists = false) {
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
+ setTimeout(DocServer.UPDATE_SERVER_CACHE, 100);
}
}
public deleteLink(linkDoc: Doc) {
@@ -154,7 +161,7 @@ export class LinkManager {
return this.relatedLinker(anchor);
} // finds all links that contain the given anchor
public getAllDirectLinks(anchor: Doc): Doc[] {
- return Array.from(Doc.GetProto(anchor)[DirectLinks] ?? []);
+ return Array.from(Doc.GetProto(anchor)[DirectLinks]);
} // finds all links that contain the given anchor
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
@@ -192,10 +199,8 @@ export class LinkManager {
public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
const a1 = Cast(linkDoc.link_anchor_1, Doc, null);
const a2 = Cast(linkDoc.link_anchor_2, Doc, null);
- if (Doc.AreProtosEqual(anchor, a1)) return a2;
- if (Doc.AreProtosEqual(anchor, a2)) return a1;
- if (Doc.AreProtosEqual(anchor, a1.annotationOn as Doc)) return a2;
- if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1;
+ if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1.annotationOn, a1))) return a2;
+ if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2.annotationOn, a2))) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
}
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
new file mode 100644
index 000000000..4dd2fcd35
--- /dev/null
+++ b/src/client/util/PingManager.ts
@@ -0,0 +1,39 @@
+import { action, observable, runInAction } from 'mobx';
+import { Networking } from '../Network';
+import { CurrentUserUtils } from './CurrentUserUtils';
+export class PingManager {
+ // create static instance and getter for global use
+ @observable static _instance: PingManager;
+ static get Instance(): PingManager {
+ return PingManager._instance;
+ }
+
+ @observable IsBeating = true;
+ private setIsBeating = action((status: boolean) => {
+ this.IsBeating = status;
+ setTimeout(this.showAlert, 100);
+ });
+
+ showAlert = () => {
+ alert(PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.');
+ };
+ sendPing = async (): Promise<void> => {
+ try {
+ const res = await Networking.PostToServer('/ping', { date: new Date() });
+ runInAction(() => (CurrentUserUtils.ServerVersion = res.message));
+ !this.IsBeating && this.setIsBeating(true);
+ } catch {
+ if (this.IsBeating) {
+ this.setIsBeating(false);
+ }
+ }
+ };
+
+ // not used now, but may need to clear interval
+ private _interval: NodeJS.Timeout | null = null;
+ INTERVAL_SECONDS = 1;
+ constructor() {
+ PingManager._instance = this;
+ this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
+ }
+}
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 247267710..afc880a7b 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -2,6 +2,7 @@ import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
+import { SettingsManager } from './SettingsManager';
@observer
export class RTFMarkup extends React.Component<{}> {
@@ -30,7 +31,7 @@ export class RTFMarkup extends React.Component<{}> {
*/
@computed get cheatSheet() {
return (
- <div style={{ textAlign: 'initial', height: '100%' }}>
+ <div style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor, textAlign: 'initial', height: '100%' }}>
<p>
<b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
@@ -52,7 +53,7 @@ export class RTFMarkup extends React.Component<{}> {
{` add a sidebar text document inline`}
</p>
<p>
- <b style={{ fontSize: 'larger' }}>{`\`\` `}</b>
+ <b style={{ fontSize: 'larger' }}>{`\`\`\` `}</b>
{` create a code snippet block`}
</p>
<p>
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index cbc465d6a..d99630f82 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -187,7 +187,6 @@ export class ReplayMovements {
} else {
// tab wasn't open - open it and play the movement
const openedColFFView = this.openTab(movement.doc);
- console.log('openedColFFView', openedColFFView);
openedColFFView && this.zoomAndPan(movement, openedColFFView);
}
diff --git a/src/client/util/ReportManager.scss b/src/client/util/ReportManager.scss
deleted file mode 100644
index 5a2f2fcad..000000000
--- a/src/client/util/ReportManager.scss
+++ /dev/null
@@ -1,88 +0,0 @@
-@import '../views/global/globalCssVariables';
-
-.issue-list-wrapper {
- position: relative;
- min-width: 250px;
- background-color: $light-blue;
- overflow-y: scroll;
-}
-
-.issue-list {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 5px;
- margin: 5px;
- border-radius: 5px;
- border: 1px solid grey;
- background-color: lightgoldenrodyellow;
-}
-
-// issue should pop up when the user hover over the issue
-.issue-list:hover {
- box-shadow: 2px;
- cursor: pointer;
- border: 3px solid #252b33;
-}
-
-.issue-content {
- background-color: white;
- padding: 10px;
- flex: 1 1 auto;
- overflow-y: scroll;
-}
-
-.issue-title {
- font-size: 20px;
- font-weight: 600;
- color: black;
-}
-
-.issue-body {
- padding: 0 10px;
- width: 100%;
- text-align: left;
-}
-
-.issue-body > * {
- margin-top: 5px;
-}
-
-.issue-body img,
-.issue-body video {
- display: block;
- max-width: 100%;
-}
-
-.report-issue-fab {
- position: fixed;
- bottom: 20px;
- right: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
-}
-
-.loading-center {
- margin: auto 0;
-}
-
-.settings-content label {
- margin-top: 10px;
-}
-
-.report-disclaimer {
- font-size: 8px;
- color: grey;
- padding-right: 50px;
- font-style: italic;
- text-align: left;
-}
-
-.flex-select {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 10px;
-}
diff --git a/src/client/util/ReportManager.tsx b/src/client/util/ReportManager.tsx
deleted file mode 100644
index 4c1020455..000000000
--- a/src/client/util/ReportManager.tsx
+++ /dev/null
@@ -1,297 +0,0 @@
-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 './ReportManager.scss';
-import { undoBatch } from './UndoManager';
-import { Octokit } from "@octokit/core";
-import { CheckBox } from '../views/search/CheckBox';
-import ReactLoading from 'react-loading';
-import ReactMarkdown from 'react-markdown';
-import rehypeRaw from 'rehype-raw';
-import remarkGfm from 'remark-gfm';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-@observer
-export class ReportManager extends React.Component<{}> {
- public static Instance: ReportManager;
- @observable private isOpen = false;
-
- private octokit: Octokit;
-
- @observable public issues: any[] = [];
- @action setIssues = action((issues: any[]) => { this.issues = issues; });
-
- // undefined is the default - null is if the user is making an issue
- @observable public selectedIssue: any = undefined;
- @action setSelectedIssue = action((issue: any) => { this.selectedIssue = issue; });
-
- // only get the open issues
- @observable public shownIssues = this.issues.filter(issue => issue.state === 'open');
-
- public updateIssueSearch = action((query: string = '') => {
- if (query === '') {
- this.shownIssues = this.issues.filter(issue => issue.state === 'open');
- return;
- }
- this.shownIssues = this.issues.filter(issue => issue.title.toLowerCase().includes(query.toLowerCase()));
- });
-
- constructor(props: {}) {
- super(props);
- ReportManager.Instance = this;
-
- this.octokit = new Octokit({
- auth: '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'
- });
- }
-
- public close = action(() => (this.isOpen = false));
- public open = action(() => {
- if (this.issues.length === 0) {
- // load in the issues if not already loaded
- this.getAllIssues()
- .then(issues => {
- this.setIssues(issues);
- this.updateIssueSearch();
- })
- .catch(err => console.log(err));
- }
- (this.isOpen = true)
- });
-
- @observable private bugTitle = '';
- @action setBugTitle = action((title: string) => { this.bugTitle = title; });
- @observable private bugDescription = '';
- @action setBugDescription = action((description: string) => { this.bugDescription = description; });
- @observable private bugType = '';
- @action setBugType = action((type: string) => { this.bugType = type; });
- @observable private bugPriority = '';
- @action setBugPriority = action((priortiy: string) => { this.bugPriority = priortiy; });
-
- // private toGithub = false;
- // will always be set to true - no alterntive option yet
- private toGithub = true;
-
- private formatTitle = (title: string, userEmail: string) => `${title} - ${userEmail.replace('@brown.edu', '')}`;
-
- public async getAllIssues() : Promise<any[]> {
- const res = await this.octokit.request('GET /repos/{owner}/{repo}/issues', {
- owner: 'brown-dash',
- repo: 'Dash-Web',
- });
-
- // 200 status means success
- if (res.status === 200) {
- return res.data;
- } else {
- throw new Error('Error getting issues');
- }
- }
-
- // turns an upload link into a servable link
- // ex:
- // C: /Users/dash/Documents/GitHub/Dash-Web/src/server/public/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2.png
- // -> http://localhost:1050/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2_l.png
- private fileLinktoServerLink = (fileLink: string) => {
- const serverUrl = 'https://browndash.com/';
-
- const regex = 'public'
- const publicIndex = fileLink.indexOf(regex) + regex.length;
-
- const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
- return finalUrl;
- }
-
- public async reportIssue() {
- if (this.bugTitle === '' || this.bugDescription === ''
- || this.bugType === '' || this.bugPriority === '') {
- alert('Please fill out all required fields to report an issue.');
- return;
- }
-
- if (this.toGithub) {
-
- const formattedLinks = (this.fileLinks ?? []).map(this.fileLinktoServerLink)
-
- const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
- owner: 'brown-dash',
- repo: 'Dash-Web',
- title: this.formatTitle(this.bugTitle, Doc.CurrentUserEmail),
- body: `${this.bugDescription} \n\nfiles:\n${formattedLinks.join('\n')}`,
- labels: [
- 'from-dash-app',
- this.bugType,
- this.bugPriority
- ]
- });
-
- // 201 status means success
- if (req.status !== 201) {
- alert('Error creating issue on github.');
- // on error, don't close the modal
- return;
- }
- }
- else {
- // if not going to github issues, not sure what to do yet...
- }
-
- // if we're down here, then we're good to go. reset the fields.
- this.setBugTitle('');
- this.setBugDescription('');
- // this.toGithub = false;
- this.setFileLinks([]);
- this.setBugType('');
- this.setBugPriority('');
- this.close();
- }
-
- @observable public fileLinks: any = [];
- @action setFileLinks = action((links: any) => { this.fileLinks = links; });
-
- private getServerPath = (link: any) => { return link.result.accessPaths.agnostic.server }
-
- private uploadFiles = (input: any) => {
- // keep null while uploading
- this.setFileLinks(null);
- // upload the files to the server
- if (input.files && input.files.length !== 0) {
- const fileArray: File[] = Array.from(input.files);
- (Networking.UploadFilesToServer(fileArray.map(file =>({file})))).then(links => {
- console.log('finshed uploading', links.map(this.getServerPath));
- this.setFileLinks((links ?? []).map(this.getServerPath));
- })
- }
-
- }
-
-
- private renderIssue = (issue: any) => {
-
- const isReportingIssue = issue === null;
-
- return isReportingIssue ?
- // report issue
- (<div className="settings-content">
- <h3 style={{ 'textDecoration': 'underline'}}>Report an Issue</h3>
- <label>Please leave a title for the bug.</label><br />
- <input type="text" placeholder='title' onChange={(e) => this.setBugTitle(e.target.value)} required/>
- <br />
- <label>Please leave a description for the bug and how it can be recreated.</label>
- <textarea placeholder='description' onChange={(e) => this.setBugDescription(e.target.value)} required/>
- <br />
- {/* {<label>Send to github issues? </label>
- <input type="checkbox" onChange={(e) => this.toGithub = e.target.checked} />
- <br /> } */}
-
- <label>Please label the issue</label>
- <div className='flex-select'>
- <select name="bugType" onChange={e => this.bugType = e.target.value}>
- <option value="" disabled selected>Type</option>
- <option value="bug">Bug</option>
- <option value="cosmetic">Poor Design or Cosmetic</option>
- <option value="documentation">Poor Documentation</option>
- </select>
-
- <select name="bigPriority" onChange={e => this.bugPriority = e.target.value}>
- <option value="" disabled selected>Priority</option>
- <option value="priority-low">Low</option>
- <option value="priority-medium">Medium</option>
- <option value="priority-high">High</option>
- </select>
- </div>
-
-
- <div>
- <label>Upload media that shows the bug (optional)</label>
- <input type="file" name="file" multiple accept='audio/*, video/*, image/*' onChange={e => this.uploadFiles(e.target)}/>
- </div>
- <br />
-
- <button onClick={() => this.reportIssue()} disabled={this.fileLinks === null} style={{ backgroundColor: this.fileLinks === null ? 'grey' : '' }}>{this.fileLinks === null ? 'Uploading...' : 'Submit'}</button>
- </div>)
- :
- // view issue
- (
- <div className='issue-container'>
- <h5 style={{'textAlign': "left"}}><a href={issue.html_url} target="_blank">Issue #{issue.number}</a></h5>
- <div className='issue-title'>
- {issue.title}
- </div>
- <ReactMarkdown children={issue.body} className='issue-body' linkTarget={"_blank"} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
- </div>
- );
- }
-
- private showReportIssueScreen = () => {
- this.setSelectedIssue(null);
- }
-
- private closeReportIssueScreen = () => {
- this.setSelectedIssue(undefined);
- }
-
- private get reportInterface() {
-
- const isReportingIssue = this.selectedIssue === null;
-
- return (
- <div className="settings-interface">
- <div className='issue-list-wrapper'>
- <h3>Current Issues</h3>
- <input type="text" placeholder='search issues' onChange={(e => this.updateIssueSearch(e.target.value))}></input><br />
- {this.issues.length === 0 ? <ReactLoading className='loading-center'/> : this.shownIssues.map(issue => <div className='issue-list' key={issue.number} onClick={() => this.setSelectedIssue(issue)}>{issue.title}</div>)}
-
- {/* <div className="settings-user">
- <button onClick={() => this.getAllIssues().then(issues => this.issues = issues)}>Poll Issues</button>
- </div> */}
- </div>
-
- <div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
- </div>
-
- <div className="issue-content" style={{'paddingTop' : this.selectedIssue === undefined ? '50px' : 'inherit'}}>
- {this.selectedIssue === undefined ? "no issue selected" : this.renderIssue(this.selectedIssue)}
- </div>
-
- <div className='report-issue-fab'>
- <span className='report-disclaimer' hidden={!isReportingIssue}>Note: issue reporting is not anonymous.</span>
- <button
- onClick={() => isReportingIssue ? this.closeReportIssueScreen() : this.showReportIssueScreen()}
- >{isReportingIssue ? 'Cancel' : 'Report New Issue'}</button>
- </div>
-
-
- </div>
- );
- }
-
- render() {
- return (
- <MainViewModal
- contents={this.reportInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: 'auto', height: '500px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }}
- />
- );
- }
-}
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 42a6493ea..87509f2ea 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -1,12 +1,11 @@
-import { Doc, DocListCast } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
-import { Cast, StrCast } from "../../fields/Types";
-import { Docs } from "../documents/Documents";
-import { ScriptingGlobals } from "./ScriptingGlobals";
+import { Doc, DocListCast } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { Cast, StrCast } from '../../fields/Types';
+import { Docs } from '../documents/Documents';
+import { ScriptingGlobals } from './ScriptingGlobals';
export class ScriptManager {
-
static _initialized = false;
private static _instance: ScriptManager;
public static get Instance(): ScriptManager {
@@ -24,11 +23,7 @@ export class ScriptManager {
}
public getAllScripts(): Doc[] {
const sdoc = ScriptManager.Instance.ScriptManagerDoc;
- if (sdoc) {
- const docs = DocListCast(sdoc.data);
- return docs;
- }
- return [];
+ return sdoc ? DocListCast(sdoc.data) : [];
}
public addScript(scriptDoc: Doc): boolean {
@@ -59,36 +54,35 @@ export class ScriptManager {
}
public static addScriptToGlobals(scriptDoc: Doc): void {
-
ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name));
- const params = Cast(scriptDoc["data-params"], listSpec("string"), []);
+ const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
const paramNames = params.reduce((o: string, p: string) => {
if (params.indexOf(p) === params.length - 1) {
- o = o + p.split(":")[0].trim();
+ o = o + p.split(':')[0].trim();
} else {
- o = o + p.split(":")[0].trim() + ",";
+ o = o + p.split(':')[0].trim() + ',';
}
return o;
- }, "" as string);
+ }, '' as string);
const f = new Function(paramNames, StrCast(scriptDoc.script));
Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
- let parameters = "(";
+ let parameters = '(';
params.forEach((element: string, i: number) => {
if (i === params.length - 1) {
- parameters = parameters + element + ")";
+ parameters = parameters + element + ')';
} else {
- parameters = parameters + element + ", ";
+ parameters = parameters + element + ', ';
}
});
- if (parameters === "(") {
+ if (parameters === '(') {
ScriptingGlobals.add(f, StrCast(scriptDoc.description));
} else {
ScriptingGlobals.add(f, StrCast(scriptDoc.description), parameters);
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0125331ec..4be9448b3 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -121,3 +121,6 @@ ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, exp
let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
return selected?.type === type || selected?.type_collection === type || !type;
});
+ScriptingGlobals.add(function deselectAll() {
+ SelectionManager.DeselectAll();
+});
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index f84ad8598..3c7c35a7e 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -3,6 +3,10 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
import './SharingManager.scss';
+import { PingManager } from './PingManager';
+import { StrCast } from '../../fields/Types';
+import { Doc } from '../../fields/Doc';
+import { SettingsManager } from './SettingsManager';
@observer
export class ServerStats extends React.Component<{}> {
@@ -39,11 +43,23 @@ export class ServerStats extends React.Component<{}> {
*/
@computed get sharingInterface() {
return (
- <div>
- <span>Active users:{this._stats?.socketMap.length}</span>
- {this._stats?.socketMap.map((user: any) => (
- <p>{user.username}</p>
- ))}
+ <div
+ style={{
+ display: 'flex',
+ height: 300,
+ width: 400,
+ background: SettingsManager.Instance?.userBackgroundColor,
+ opacity: 0.6,
+ }}>
+ <div style={{ width: 300, height: 100, margin: 'auto', display: 'flex', flexDirection: 'column' }}>
+ {PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.'}
+
+ <br />
+ <span>Active users:{this._stats?.socketMap.length}</span>
+ {this._stats?.socketMap.map((user: any) => (
+ <p>{user.username}</p>
+ ))}
+ </div>
</div>
);
}
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index 1289ca2b4..bca649bc3 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -2,41 +2,20 @@
.settings-interface {
//background-color: whitesmoke !important;
- color: grey;
width: 450px;
-
- button {
- background: #315a96;
- outline: none;
- border-radius: 5px;
- border: 0px;
- color: #fcfbf7;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- padding: 10px;
- margin: 10px;
- transition: transform 0.2s;
- margin: 2px;
- }
}
.settings-title {
font-size: 25px;
font-weight: bold;
padding-right: 10px;
- color: black;
}
.settings-username {
font-size: 12px;
padding-right: 15px;
- color: black;
margin-top: 10px;
margin-bottom: 10px;
- /* right: 135; */
- // position: absolute;
- // left: 243;
}
.settings-section {
@@ -49,7 +28,6 @@
font-size: 16;
font-weight: bold;
text-align: left;
- color: black;
width: 80;
margin-right: 50px;
}
@@ -61,40 +39,10 @@
.password-content {
display: flex;
+ width: 100%;
flex-direction: column;
-
- .password-content-inputs {
- width: 100;
- // margin-bottom: 10px;
- font-size: 10px;
-
- .password-inputs {
- border: 1px solid rgb(160, 160, 160);
- margin-bottom: 8px;
- width: 130;
- color: black;
- border-radius: 5px;
- padding: 7px;
- }
- }
-
- .password-content-buttons {
- //margin-left: 84px;
- //width: 100;
- padding: 7px;
-
- .password-submit {
- //margin-left: 85px;
- margin-top: 5px;
- }
-
- .password-forgot {
- //margin-left: 65px;
- //margin-top: -20px;
- font-size: 12px;
- white-space: nowrap;
- }
- }
+ align-items: flex-start;
+ gap: 5px;
}
.accounts-content {
@@ -103,7 +51,6 @@
.modes-content {
display: flex;
- margin-left: 10px;
font-size: 12px;
.modes-select {
@@ -111,7 +58,6 @@
width: 80%;
height: 35px;
margin-right: 10px;
- color: black;
border-radius: 5px;
&:hover {
@@ -135,12 +81,6 @@
}
}
- .playground-text {
- color: black;
- margin-right: 10px;
- margin-top: 2;
- }
-
.acl-text {
color: black;
margin-top: 2;
@@ -172,12 +112,11 @@
.appearances-content {
display: flex;
- margin-top: 4px;
- color: black;
font-size: 10px;
.preferences-color {
display: flex;
+ align-items: center;
margin-top: 2px;
.preferences-color-text {
@@ -197,7 +136,6 @@
margin-top: 2px;
.preferences-font-text {
- color: black;
margin-top: 4;
margin-right: 4;
margin-bottom: 2px;
@@ -212,7 +150,6 @@
.font-select {
height: 35px;
- color: black;
font-size: 9;
margin-right: 6;
border-radius: 5px;
@@ -225,7 +162,6 @@
.size-select {
height: 35px;
- color: black;
font-size: 9;
border-radius: 5px;
width: 30%;
@@ -237,7 +173,6 @@
}
.preferences-check {
- color: black;
margin-right: 4;
margin-bottom: -3;
margin-left: 5;
@@ -252,40 +187,17 @@
display: flex;
flex-direction: column;
- button {
- width: auto;
- align-self: center;
- background: #252b33;
- margin-right: 15px;
-
- //margin-top: 4px;
-
- &:hover {
- background: $medium-gray;
- }
- }
-
- // .delete-button {
- // background: rgb(227, 86, 86);
- // }
.close-button {
position: absolute;
- right: 1em;
- top: 1em;
- cursor: pointer;
+ right: 2px;
+ top: 2px;
}
- // .logout-button {
- // right: 355;
- // position: absolute;
- // }
.settings-content {
- background: #e4e4e4;
- //border-radius: 6px;
padding: 10px;
- //width: 560px;
+ width: 500px;
flex: 1 1 auto;
}
@@ -296,11 +208,8 @@
.error-text {
color: #c40233;
- width: 300;
- margin-left: -20;
font-size: 10;
margin-bottom: 4;
- margin-top: -3;
}
.success-text {
@@ -317,7 +226,6 @@
}
h1 {
- color: #121721;
text-transform: uppercase;
letter-spacing: 2px;
font-size: 19;
@@ -346,7 +254,6 @@
.padding {
padding: 0 0 0 20px;
- color: black;
}
}
}
@@ -357,21 +264,19 @@
min-height: 250px;
height: 100%;
width: 100%;
-
- .settings-content {
- background-color: $off-white;
- }
}
.settings-panel {
position: relative;
min-width: 150px;
- background-color: $light-blue;
.settings-user {
position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
bottom: 10px;
- text-align: center;
left: 0;
right: 0;
@@ -388,16 +293,11 @@
.settings-tabs {
// font-size: 16px;
font-weight: 600;
- color: black;
.tab-control {
padding: 10px;
border-bottom: 1px solid #9f9e9e;
cursor: pointer;
-
- &.active {
- background-color: #fdfdfd;
- }
}
}
@@ -416,20 +316,31 @@
.tab-content {
display: flex;
+ flex-flow: row wrap;
+ gap: 10px;
+ overflow: hidden;
.tab-column {
- flex: 0 0 50%;
+ flex: 0 0 calc(50% - 10px);
+ flex-direction: column;
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
.tab-column-title {
- color: black;
font-size: 16px;
font-weight: bold;
- margin-bottom: 16px;
+ margin-bottom: 10px;
}
.tab-column-title,
.tab-column-content {
- padding-left: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 10px;
+ width: 100%;
}
}
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index f886ce2ca..a2e5e54fe 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,18 +1,19 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { ColorState, SketchPicker } from 'react-color';
+import { BsGoogle } from 'react-icons/bs';
+import { FaFillDrip, FaPalette } from 'react-icons/fa';
import { Doc } from '../../fields/Doc';
-import { Id } from '../../fields/FieldSymbols';
+import { DashVersion } from '../../fields/DocSymbols';
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 { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
import { GroupManager } from './GroupManager';
import './SettingsManager.scss';
import { undoBatch } from './UndoManager';
@@ -21,9 +22,12 @@ export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
export enum ColorScheme {
- Dark = '-Dark',
- Light = '-Light',
- System = '-MatchSystem',
+ Dark = 'Dark',
+ Light = 'Light',
+ CoolBlue = 'Cool Blue',
+ Cupcake = 'Cupcake',
+ System = 'Match System',
+ Custom = 'Custom',
}
export enum freeformScrollMode {
@@ -50,8 +54,8 @@ export class SettingsManager extends React.Component<{}> {
@computed get backgroundColor() {
return Doc.UserDoc().activeCollectionBackground;
}
- @computed get colorScheme() {
- return Doc.ActiveDashboard?.colorScheme;
+ @computed get userTheme() {
+ return Doc.UserDoc().userTheme;
}
constructor(props: {}) {
@@ -73,14 +77,31 @@ export class SettingsManager extends React.Component<{}> {
}
};
- @undoBatch selectUserMode = action((e: React.ChangeEvent) => (Doc.noviceMode = (e.currentTarget as any)?.value === 'Novice'));
+ @computed get userColor() {
+ return StrCast(Doc.UserDoc().userColor);
+ }
+
+ @computed get userVariantColor() {
+ return StrCast(Doc.UserDoc().userVariantColor);
+ }
+
+ @computed get userBackgroundColor() {
+ return StrCast(Doc.UserDoc().userBackgroundColor);
+ }
+
+ @undoBatch selectUserMode = action((mode: string) => (Doc.noviceMode = mode === 'Novice'));
@undoBatch changelayout_showTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().layout_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 changeFontFamily = action((font: string) => (Doc.UserDoc().fontFamily = font));
+ @undoBatch changeFontSize = action((val: number) => (Doc.UserDoc().fontSize = val));
+ @undoBatch switchUserBackgroundColor = action((color: string) => {
+ Doc.UserDoc().userBackgroundColor = color;
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
+ });
+ @undoBatch switchUserColor = action((color: string) => {
+ Doc.UserDoc().userColor = color;
+ });
+ @undoBatch switchUserVariantColor = action((color: string) => {
+ Doc.UserDoc().userVariantColor = color;
});
@undoBatch playgroundModeToggle = action(() => {
this.playgroundMode = !this.playgroundMode;
@@ -92,82 +113,91 @@ export class SettingsManager extends React.Component<{}> {
@undoBatch
@action
- changeColorScheme = action((e: React.ChangeEvent) => {
- const activeDashboard = Doc.ActiveDashboard;
- if (!activeDashboard) return;
- const scheme: ColorScheme = (e.currentTarget as any).value;
+ changeColorScheme = action((scheme: string) => {
+ Doc.UserDoc().userTheme = scheme;
switch (scheme) {
case ColorScheme.Light:
- 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' });
+ this.switchUserColor('#323232');
+ this.switchUserBackgroundColor('#DFDFDF');
+ this.switchUserVariantColor('#BDDDF5');
break;
case ColorScheme.Dark:
- activeDashboard.colorScheme = ColorScheme.Dark;
- addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: 'black !important' });
+ this.switchUserColor('#DFDFDF');
+ this.switchUserBackgroundColor('#323232');
+ this.switchUserVariantColor('#4476F7');
+ break;
+ case ColorScheme.CoolBlue:
+ this.switchUserColor('#ADEAFF');
+ this.switchUserBackgroundColor('#060A15');
+ this.switchUserVariantColor('#3C51FF');
+ break;
+ case ColorScheme.Cupcake:
+ this.switchUserColor('#3BC7FF');
+ this.switchUserBackgroundColor('#fffdf7');
+ this.switchUserVariantColor('#FFD7F3');
+ break;
+ case ColorScheme.Custom:
break;
case ColorScheme.System:
default:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
- 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)
+ e.matches ? ColorScheme.Dark : ColorScheme.Light; // 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 colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
- const schemeMap = ['Light', 'Dark', 'Match system'];
-
+ const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.Cupcake, ColorScheme.CoolBlue, ColorScheme.Custom, ColorScheme.System];
+ const schemeMap = ['Light', 'Dark', 'Cupcake', 'Cool Blue', 'Custom', 'Match System'];
+ const userTheme = StrCast(Doc.UserDoc().userTheme);
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 style={{ width: '100%' }}>
+ <Dropdown
+ formLabel="Theme"
+ size={Size.SMALL}
+ type={Type.TERT}
+ selectedVal={userTheme}
+ setSelectedVal={scheme => this.changeColorScheme(scheme as string)}
+ items={colorSchemes.map((scheme, i) => ({
+ text: schemeMap[i],
+ val: scheme,
+ }))}
+ dropdownType={DropdownType.SELECT}
+ color={this.userColor}
+ fillWidth
+ />
+ {userTheme === ColorScheme.Custom && (
+ <Group formLabel="Custom Theme">
+ <ColorPicker
+ tooltip={'User Color'} //
+ color={this.userColor}
+ type={Type.SEC}
+ icon={<FaFillDrip />}
+ selectedColor={this.userColor}
+ setSelectedColor={this.switchUserColor}
+ setFinalColor={this.switchUserColor}
+ />
+ <ColorPicker
+ tooltip={'User Background Color'}
+ color={this.userColor}
+ type={Type.SEC}
+ icon={<FaPalette />}
+ selectedColor={this.userBackgroundColor}
+ setSelectedColor={this.switchUserBackgroundColor}
+ setFinalColor={this.switchUserBackgroundColor}
+ />
+ <ColorPicker
+ tooltip={'User Variant Color'}
+ color={this.userColor}
+ type={Type.SEC}
+ icon={<FaPalette />}
+ selectedColor={this.userVariantColor}
+ setSelectedColor={this.switchUserVariantColor}
+ setFinalColor={this.switchUserVariantColor}
+ />
+ </Group>
+ )}
</div>
);
}
@@ -175,30 +205,60 @@ export class SettingsManager extends React.Component<{}> {
@computed get formatsContent() {
return (
<div className="prefs-content">
- <div>
- <input type="checkbox" onChange={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')} checked={Doc.UserDoc().layout_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 => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} />
- <div className="preferences-check">Show button labels</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())} checked={FontIconBox.GetRecognizeGestures()} />
- <div className="preferences-check">Recognize ink Gestures</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} checked={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} />
- <div className="preferences-check">Hide Labels In Ink Shapes</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} checked={BoolCast(Doc.UserDoc().openInkInLightbox)} />
- <div className="preferences-check">Open Ink Docs in Lightbox</div>
- </div>
+ <Toggle
+ formLabel={'Show document header'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')}
+ toggleStatus={Doc.UserDoc().layout_showTitle !== undefined}
+ size={Size.XSMALL}
+ color={this.userColor}
+ />
+ <Toggle
+ formLabel={'Show Full Toolbar'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])}
+ toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])}
+ size={Size.XSMALL}
+ color={this.userColor}
+ />
+ <Toggle
+ formLabel={'Show Button Labels'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())}
+ toggleStatus={FontIconBox.GetShowLabels()}
+ size={Size.XSMALL}
+ color={this.userColor}
+ />
+ <Toggle
+ formLabel={'Recognize Ink Gestures'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())}
+ toggleStatus={FontIconBox.GetRecognizeGestures()}
+ size={Size.XSMALL}
+ color={this.userColor}
+ />
+ <Toggle
+ formLabel={'Hide Labels In Ink Shapes'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
+ toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
+ size={Size.XSMALL}
+ color={this.userColor}
+ />
+ <Toggle
+ formLabel={'Open Ink Docs in Lightbox'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
+ toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
+ size={Size.XSMALL}
+ color={this.userColor}
+ />
</div>
);
}
@@ -224,25 +284,32 @@ export class SettingsManager extends React.Component<{}> {
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>
- <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 className="tab-column">
+ <div className="tab-column-title">Text</div>
+ <div className="tab-column-content">
+ {/* <NumberInput/> */}
+ <Group formLabel={'Default Font'}>
+ <NumberDropdown color={this.userColor} numberDropdownType={'input'} min={0} max={50} step={2} type={Type.TERT} number={0} unit={'px'} setNumber={() => {}} />
+ <Dropdown
+ items={fontFamilies.map(val => {
+ return {
+ text: val,
+ val: val,
+ style: {
+ fontFamily: val,
+ },
+ };
+ })}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ selectedVal={StrCast(Doc.UserDoc().fontFamily)}
+ setSelectedVal={val => {
+ this.changeFontFamily(val as string);
+ }}
+ color={this.userColor}
+ fillWidth
+ />
+ </Group>
</div>
</div>
</div>
@@ -250,8 +317,7 @@ export class SettingsManager extends React.Component<{}> {
}
@action
- changeVal = (e: React.ChangeEvent, pass: string) => {
- const value = (e.target as any).value;
+ changeVal = (value: string, pass: string) => {
switch (pass) {
case 'curr':
this.curr_password = value;
@@ -268,20 +334,12 @@ export class SettingsManager extends React.Component<{}> {
@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>
- </div>
+ <EditableText placeholder="Current password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
+ <EditableText placeholder="New password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
+ <EditableText placeholder="Confirm new password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
+ {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
+ <Button type={Type.SEC} text={'Forgot Password'} color={this.userColor} />
+ <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={this.userColor} />
</div>
);
}
@@ -289,9 +347,7 @@ export class SettingsManager extends React.Component<{}> {
@computed get accountOthersContent() {
return (
<div className="account-others-content">
- <button onClick={this.googleAuthorize} value="data">
- Authorize Google Acc
- </button>
+ <Button type={Type.TERT} text={'Connect to Google'} iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
</div>
);
}
@@ -311,7 +367,7 @@ export class SettingsManager extends React.Component<{}> {
);
}
- setFreeformScrollMode = (mode: freeformScrollMode) => {
+ setFreeformScrollMode = (mode: string) => {
Doc.UserDoc().freeformScrollMode = mode;
};
@@ -321,45 +377,65 @@ export class SettingsManager extends React.Component<{}> {
<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>
+ <Dropdown
+ formLabel={'Mode'}
+ items={[
+ {
+ text: 'Novice',
+ description: 'Novice mode is a user-friendly setting designed to cater to those who are new to Dash',
+ val: 'Novice',
+ },
+ {
+ text: 'Developer',
+ description:
+ 'Developer mode is an advanced setting that grants you greater control and access to the underlying mechanics and tools of a software or system. Developer mode is still under development as there are experimental features.',
+ val: 'Developer',
+ },
+ ]}
+ selectedVal={Doc.noviceMode ? 'Novice' : 'Developer'}
+ setSelectedVal={val => {
+ this.selectUserMode(val as string);
+ }}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ placement="bottom-start"
+ color={this.userColor}
+ fillWidth
+ />
+ <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={this.userColor} />
</div>
- <div className="tab-column-title" style={{ marginTop: 10, marginBottom: 0 }}>
- Freeform scrolling
+ <div className="tab-column-title" style={{ marginTop: 20, marginBottom: 10 }}>
+ Freeform Navigation
</div>
<div className="tab-column-content">
- <button style={{ backgroundColor: Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? 'blue' : '' }} onClick={() => this.setFreeformScrollMode(freeformScrollMode.Pan)}>
- Scroll to pan
- </button>
- <div>
- <div>Scrolling pans canvas, shift + scrolling zooms</div>
- </div>
- <button style={{ backgroundColor: Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom ? 'blue' : '' }} onClick={() => this.setFreeformScrollMode(freeformScrollMode.Zoom)}>
- Scroll to zoom
- </button>
- <div>Scrolling zooms canvas</div>
+ <Dropdown
+ formLabel={'Scroll Mode'}
+ items={[
+ {
+ text: 'Scroll to Pan',
+ description: 'Scrolling pans canvas, shift + scrolling zooms',
+ val: freeformScrollMode.Pan,
+ },
+ {
+ text: 'Scroll to Zoom',
+ description: 'Scrolling zooms canvas',
+ val: freeformScrollMode.Zoom,
+ },
+ ]}
+ selectedVal={StrCast(Doc.UserDoc().freeformScrollMode)}
+ setSelectedVal={val => this.setFreeformScrollMode(val as string)}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ placement="bottom-start"
+ color={this.userColor}
+ />
</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.defaultAclPrivate)} onChange={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} />
- <div className="acl-text">Default access private</div>
- </div>
+ <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={this.userColor} />
+ <Toggle toggleType={ToggleType.SWITCH} formLabel={'Default access private'} color={this.userColor} toggleStatus={BoolCast(Doc.defaultAclPrivate)} onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} />
</div>
</div>
</div>
@@ -376,31 +452,41 @@ export class SettingsManager extends React.Component<{}> {
{ title: 'Appearance', ele: this.appearanceContent },
{ title: 'Text', ele: this.textContent },
];
-
return (
<div className="settings-interface">
- <div className="settings-panel">
+ <div className="settings-panel" style={{ background: this.userColor }}>
<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>
- ))}
+ {tabs.map(tab => {
+ const isActive = this.activeTab === tab.title;
+ return (
+ <div
+ key={tab.title}
+ style={{
+ background: isActive ? this.userBackgroundColor : this.userColor,
+ color: isActive ? this.userColor : this.userBackgroundColor,
+ }}
+ className={'tab-control ' + (isActive ? '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'))}>
- {Doc.GuestDashboard ? 'Exit' : 'Log Out'}
- </button>
+ <div style={{ color: this.userBackgroundColor }}>{DashVersion}</div>
+ <div className="settings-username" style={{ color: this.userBackgroundColor }}>
+ {Doc.CurrentUserEmail}
+ </div>
+ <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={this.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} />
</div>
</div>
- <div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ <div className="close-button">
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={this.userColor} />
</div>
- <div className="settings-content">
+ <div className="settings-content" style={{ color: this.userColor, background: this.userBackgroundColor }}>
{tabs.map(tab => (
<div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
{tab.ele}
@@ -418,7 +504,7 @@ export class SettingsManager extends React.Component<{}> {
isDisplayed={this.isOpen}
interactive={true}
closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: '500px', height: '300px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }}
+ dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }}
/>
);
}
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 932e94664..c9f86f0e1 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -2,39 +2,61 @@
width: 600px;
// height: 360px;
- .overlay {
- transform: translate(-20px, -20px);
- }
-
- select {
+ .select {
text-align: justify;
text-align-last: end
}
.sharing-contents {
+ padding: 10px;
display: flex;
flex-direction: column;
.close-button {
position: absolute;
- right: 1em;
- top: 1em;
- cursor: pointer;
- z-index: 999;
+ right: 2px;
+ top: 2px;
+ }
+
+ .share-title {
+ display: inline-flex;
+ gap: 5px;
+
+ .share-info {
+ align-self: center;
+ cursor: pointer;
+ }
+ }
+
+ .share-copy-link {
+ border-radius: 4px;
+ margin-top: -15px;
+ margin-bottom: 15px;
+ width: fit-content;
}
.share-container {
+
.share-setup {
display: flex;
margin-bottom: 20px;
align-items: center;
- height: 36;
+ max-height: 28;
.user-search {
+ max-height: 30;
width: 90%;
+ display: inline-flex;
+ flex-direction: row;
+ border: 2px solid;
+ border-radius: 4px;
+ border-color: inherit;
+ background: white;
+ color: black;
input {
- height: 30;
+ height: 20px;
+ width: 100px;
}
}
@@ -44,12 +66,14 @@
outline: none;
text-align: justify; // for Edge
text-align-last: end;
+ font-size: 13px;
+ min-width: 90px;
+ height: 28;
+ margin-left: 2px;
}
.share-button {
- height: 105%;
- margin-left: 2%;
- background-color: black;
+ margin-left: 3%;
}
}
@@ -76,15 +100,16 @@
float: right;
align-items: baseline;
margin-top: -12;
+ margin-bottom: 10;
.layoutDoc-acls,
.myDocs-acls {
flex-direction: column;
- margin-right: 12;
label {
font-weight: normal;
font-style: italic;
+ padding-right: 12;
}
input {
@@ -102,7 +127,9 @@
.group-container {
width: 50%;
display: flex;
+ top:0;
flex-direction: column;
+ border-radius: 4px;
.user-sort {
text-align: left;
@@ -119,10 +146,12 @@
.groups-list,
.users-list {
font-style: italic;
- background: #e8e8e8;
+ border: 2px solid;
padding-left: 10px;
padding-right: 10px;
- width: 100%;
+ width: 97%;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
overflow-y: scroll;
overflow-x: hidden;
text-align: left;
@@ -131,7 +160,6 @@
align-items: center;
text-align: center;
justify-content: center;
- color: black;
height: 250px;
margin: 0 2;
@@ -146,7 +174,6 @@
outline: none;
border-radius: 5px;
border: 0px;
- color: #fcfbf7;
text-transform: none;
letter-spacing: 2px;
font-size: 75%;
@@ -165,11 +192,10 @@
font-size: 20px;
text-align: left;
margin: 0 0 20px 0;
- color: black;
}
.hr-substitute {
- border: solid black 0.5px;
+ border: solid 0.5px;
margin-top: 20px;
}
@@ -190,88 +216,186 @@
}
}
+ .title-individual{
+ height: 25px;
+ padding-left: 2;
+ width: 97%;
+ border-radius: 4px;
+ margin-top: 10px;
+ margin-left: -8px;
+ font-size: 14;
+ margin-bottom: -4;
+ border: 2px solid;
+ border-bottom: none;
+ align-items: center;
+ display: flex;
+ }
+
+ .title-group{
+ height: 25px;
+ padding-left: 2;
+ width: 97%;
+ border-radius: 4px;
+ margin-top: 10px;
+ margin-left: -8px;
+ font-size: 14;
+ margin-bottom: -4;
+ border: 2px solid;
+ border-bottom: none;
+ align-items: center;
+ display: flex;
+ }
+
.container {
display: flex;
position: relative;
margin-top: 5px;
- margin-bottom: 10px;
+ margin-left: -5px;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- width: 100%;
+ width: 97%;
text-align: left;
font-style: normal;
- font-size: 14;
+ font-size: 12.5;
font-weight: normal;
- padding: 0;
- align-items: center;
+
+ padding: 3px;
+ border-bottom: 0.5px solid;
.group-info {
cursor: pointer;
}
&:hover .padding {
+ overflow-x: unset;
white-space: unset;
+ overflow-wrap: break-word;
}
.padding {
- padding: 0 10px 0 0;
- color: black;
+ max-width: 150px;
+ overflow-x: hidden;
+ display: inline-block;
text-overflow: ellipsis;
- overflow: hidden;
white-space: nowrap;
- max-width: 40%;
}
.permissions-dropdown {
- border: none;
- height: 25;
- background-color: #e8e8e8;
+ display: flex;
+ align-items: flex-end;
+ text-align: right;
+ margin-left: auto;
+ margin-right: -12px;
+
}
.edit-actions {
display: flex;
position: absolute;
- right: -10;
+ align-items: flex-end;
+ right: -10;
}
+ }
+
+ .permissions-dropdown-None{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: grey;
+ color: rgb(71, 71, 71);
+ border-radius: 6px;
+ border: 1px solid rgb(71, 71, 71);
+ }
+ .permissions-dropdown-Edit,
+ .permissions-dropdown-Admin {
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(254, 254, 199);
+ color: rgb(75, 75, 5);
+ border-radius: 6px;
+ border: 1px solid rgb(75, 75, 5);
+ }
+ .permissions-dropdown-Augment{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(208, 255, 208);
+ color:rgb(19, 80, 19);
+ border-radius: 6px;
+ border: 1px solid rgb(19, 80, 19);
}
+ .permissions-dropdown-View{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(213, 213, 255);
+ color: rgb(25, 25, 101);
+ border-radius: 6px;
+ border: 1px solid rgb(25, 25, 101);
+ }
+ .permissions-dropdown-Not-Shared{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(255, 207, 207);
+ color: rgb(138, 47, 47);
+ border-radius: 6px;
+ border: 1px solid rgb(138, 47, 47);
+ }
.no-users {
margin-top: 20px;
}
- .link-container {
- display: flex;
- flex-direction: row;
- margin-bottom: 10px;
- margin-left: auto;
- margin-right: auto;
-
- .link-box,
- .copy {
- padding: 10px;
- border-radius: 10px;
- padding: 10px;
- border: solid black 1px;
- }
-
- .link-box {
- background: white;
- color: blue;
- text-decoration: underline;
- }
-
- .copy {
- margin-left: 20px;
- cursor: alias;
- border-radius: 50%;
- width: 42px;
- height: 42px;
- transition: 1.5s all ease;
- padding-top: 12px;
- }
- }
+ // .link-container {
+ // display: flex;
+ // flex-direction: row;
+ // margin-bottom: 10px;
+ // margin-left: auto;
+ // margin-right: auto;
+
+ // .link-box,
+ // .copy {
+ // padding: 10px;
+ // border-radius: 10px;
+ // padding: 10px;
+ // border: solid black 1px;
+ // }
+
+ // .link-box {
+ // background: white;
+ // color: blue;
+ // text-decoration: underline;
+ // }
+
+ // .copy {
+ // margin-left: 20px;
+ // cursor: alias;
+ // border-radius: 50%;
+ // width: 42px;
+ // height: 42px;
+ // transition: 1.5s all ease;
+ // padding-top: 12px;
+ // }
+ // }
} \ No newline at end of file
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 97e64ab71..6171c01d7 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,15 +1,16 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { intersection } from 'lodash';
+import { Button, IconButton, Size, Type } from 'browndash-components';
+import { concat, 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 { Doc, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc';
-import { AclAdmin, AclPrivate, DocAcl, AclUnset, DocData } from '../../fields/DocSymbols';
+import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
+import { FieldLoader } from '../../fields/FieldLoader';
import { Id } from '../../fields/FieldSymbols';
-import { List } from '../../fields/List';
-import { NumCast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
import { Utils } from '../../Utils';
import { DocServer } from '../DocServer';
@@ -21,8 +22,11 @@ import { SearchBox } from '../views/search/SearchBox';
import { DocumentManager } from './DocumentManager';
import { GroupManager, UserOptions } from './GroupManager';
import { GroupMemberView } from './GroupMemberView';
+import { LinkManager } from './LinkManager';
import { SelectionManager } from './SelectionManager';
+import { SettingsManager } from './SettingsManager';
import './SharingManager.scss';
+import { undoable } from './UndoManager';
export interface User {
email: string;
@@ -47,6 +51,7 @@ const indType = '!indType/';
const groupType = '!groupType/';
const storage = 'data';
+const dashStorage = 'data_dashboards';
/**
* A user who also has a sharing doc.
@@ -73,13 +78,14 @@ export class SharingManager extends React.Component<{}> {
@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
@observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component)
@observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)
private populating: boolean = false; // whether the list of users is populating or not
+ @observable private upgradeNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is upgrade all
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
+ @observable private _buttonDown = false;
// private get linkVisible() {
// return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
@@ -93,6 +99,7 @@ export class SharingManager extends React.Component<{}> {
DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = this.targetDoc !== undefined;
this.permissions = SharingPermissions.Augment;
+ this.upgradeNested = true;
});
};
@@ -108,6 +115,7 @@ export class SharingManager extends React.Component<{}> {
}),
500
);
+ this.layoutDocAcls = false;
});
constructor(props: {}) {
@@ -130,6 +138,7 @@ export class SharingManager extends React.Component<{}> {
this.populating = true;
const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
+ runInAction(() => (FieldLoader.ServerLoadStatus.message = 'users'));
const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
raw.map(
action((newUser: User) => {
@@ -138,7 +147,7 @@ export class SharingManager extends React.Component<{}> {
if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
if (!this.users.find(user => user.user.email === newUser.email)) {
this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
- // LinkManager.addLinkDB(sharer.linkDatabase);
+ //LinkManager.addLinkDB(linkDatabase);
}
}
})
@@ -150,79 +159,44 @@ export class SharingManager extends React.Component<{}> {
/**
* Shares the document with a user.
*/
- setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => {
+ setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
const { user, sharingDoc } = recipient;
const target = targetDoc || this.targetDoc!;
const acl = `acl-${normalizeEmail(user.email)}`;
- const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
- 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 => (this.layoutDocAcls ? doc : doc[DocData]))
- .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);
-
- this.setDashboardBackground(doc, permission as SharingPermissions);
- if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc);
- return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc);
- })
- .some(success => !success);
- };
+ const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
+ if (permission !== SharingPermissions.None) {
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
+ } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
+ });
+ }, 'set Doc permissions');
/**
* Sets the permission on the target for the group.
* @param group
* @param permission
*/
- setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
+ setInternalGroupSharing = undoable((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(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 => (this.layoutDocAcls ? doc : doc[DocData]))
- .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;
- }
+ const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
- this.setDashboardBackground(doc, permission as SharingPermissions);
+ const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
- 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) {
+ Doc.AddDocToList(group, 'docsShared', 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.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
- })
- .some(success => !success);
- }
- })
- .some(success => success);
- };
+ this.users
+ .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
+ .forEach(({ user, sharingDoc }) => {
+ if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
+ });
+ }
+ });
+ }, 'set group permissions');
/**
* Shares the documents shared with a group with a new user who has been added to that group.
@@ -237,7 +211,13 @@ export class SharingManager extends React.Component<{}> {
else {
DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => !userdocs?.includes(doc));
+ const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
filtered && userdocs?.push(...filtered);
})
);
@@ -248,44 +228,23 @@ export class SharingManager extends React.Component<{}> {
/**
* Called from the properties sidebar to change permissions of a user.
*/
- shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => {
- if (shareWith !== 'Public' && shareWith !== 'Override') {
+ shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
+ if (layout) this.layoutDocAcls = true;
+ if (shareWith !== 'Guest') {
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 this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
});
} 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);
- this.setDashboardBackground(doc, permission as SharingPermissions);
- });
- }
- };
-
- /**
- * Sets the background of the Dashboard if it has been shared as a visual indicator
- */
- setDashboardBackground = (doc: Doc, permission: SharingPermissions) => {
- if (Doc.IndexOf(doc, DocListCast(Doc.MyDashboards.data)) !== -1) {
- if (permission !== SharingPermissions.None) {
- doc.isShared = true;
- doc.backgroundColor = 'green';
- } else {
- const acls = doc[DocData][DocAcl];
- if (
- Object.keys(acls)
- .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && key !== 'acl-Me')
- .every(key => [AclUnset, AclPrivate].includes(acls[key]))
- ) {
- doc.isShared = undefined;
- doc.backgroundColor = undefined;
+ if (GetEffectiveAcl(doc) === AclAdmin) {
+ distributeAcls(`acl-${shareWith}`, permission, doc, undefined);
}
- }
+ });
}
- };
+ this.layoutDocAcls = false;
+ }, 'sidebar set permissions');
/**
* Removes the documents shared with a user through a group when the user is removed from the group.
@@ -295,13 +254,19 @@ export class SharingManager extends React.Component<{}> {
removeMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
- if (group.docsShared) {
+ if (group.docsShared && user) {
DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
DocListCastAsync(group.docsShared).then(dl => {
const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
userdocs?.splice(0, userdocs.length, ...remaining);
})
);
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
}
};
@@ -311,11 +276,9 @@ export class SharingManager extends React.Component<{}> {
*/
removeGroup = (group: Doc) => {
if (group.docsShared) {
- const dashboards = DocListCast(Doc.MyDashboards.data);
DocListCast(group.docsShared).forEach(doc => {
const acl = `acl-${StrCast(group.title)}`;
- const isDashboard = dashboards.indexOf(doc) !== -1;
- distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined, isDashboard);
+ distributeAcls(acl, SharingPermissions.None, doc);
const members: string[] = JSON.parse(StrCast(group.members));
const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -331,37 +294,26 @@ export class SharingManager extends React.Component<{}> {
// return;
// }
// targetDoc["acl-" + PublicKey] = permission;
- // }
-
- // private get sharingUrl() {
- // if (!this.targetDoc) {
- // return undefined;
- // }
- // const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
- // return `${baseUrl}?sharing=true`;
- // }
+ // }s
- // copy = action(() => {
- // if (this.sharingUrl) {
- // Utils.CopyText(this.sharingUrl);
- // this.copied = true;
- // }
- // });
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = (e: any) => {
+ Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]));
+ };
/**
* Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
*/
- private sharingOptions(uniform: boolean, override?: boolean) {
- const dropdownValues: string[] = Object.values(SharingPermissions);
+ private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
+ const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
if (!uniform) dropdownValues.unshift('-multiple-');
- if (!override) dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
- return dropdownValues
- .filter(permission => !Doc.noviceMode || ![SharingPermissions.SelfEdit].includes(permission as any))
- .map(permission => (
- <option key={permission} value={permission}>
- {permission}
- </option>
- ));
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
}
private focusOn = (contents: string) => {
@@ -406,38 +358,43 @@ export class SharingManager extends React.Component<{}> {
/**
* Handles changes in the permission chosen to share with someone with
*/
- @action
- handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
- this.permissions = event.currentTarget.value as SharingPermissions;
- };
+ handlePermissionsChange = undoable(
+ action((event: React.ChangeEvent<HTMLSelectElement>) => {
+ this.permissions = event.currentTarget.value as SharingPermissions;
+ }),
+ 'permission change'
+ );
/**
* Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
*/
- @action
- share = () => {
- if (this.selectedUsers) {
- this.selectedUsers.forEach(user => {
- if (user.value.includes(indType)) {
- this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
- } else {
- this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
- }
- });
-
- 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.taskCompleted = true;
- setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
- 2000
- );
+ share = undoable(
+ action(() => {
+ if (this.selectedUsers) {
+ this.selectedUsers.forEach(user => {
+ if (user.value.includes(indType)) {
+ this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
+ } else {
+ this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+ }
+ });
+
+ 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.taskCompleted = true;
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2000
+ );
- this.selectedUsers = null;
- }
- };
+ this.layoutDocAcls = false;
+ this.selectedUsers = null;
+ }
+ }),
+ 'share Doc'
+ );
/**
* Sorting algorithm to sort users.
@@ -464,6 +421,7 @@ export class SharingManager extends React.Component<{}> {
if (!this.targetDoc) return null;
TraceMobx();
const groupList = GroupManager.Instance?.allGroups || [];
+
const sortedUsers = this.users
.slice()
.sort(this.sortUsers)
@@ -485,8 +443,7 @@ export class SharingManager extends React.Component<{}> {
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?.[DocData]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DocData]));
+ let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
if (this.myDocAcls) {
const newDocs: Doc[] = [];
@@ -501,26 +458,32 @@ 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 : doc[DocData])).map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl])));
+ const commonKeys = intersection(docs).reduce((list, doc) => (doc?.[DocAcl] ? [...list, ...Object.keys(doc[DocAcl])] : list), [] as string[]);
// 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))
+ const userListContents = users
+ // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ .filter(({ user }) => docs[0]?.author !== user.email)
.map(({ user, linkDatabase, sharingDoc, userColor }) => {
const userKey = `acl-${normalizeEmail(user.email)}`;
- const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
- const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+ const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
+ // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+ let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]);
+ 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)}>
+ <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
{this.sharingOptions(uniform)}
</select>
) : (
- <div className={'permissions-dropdown'}>{permissions}</div>
+ <div className={`permissions-dropdown-${permissions}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -531,6 +494,9 @@ export class SharingManager extends React.Component<{}> {
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.
+ const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
+ const curUserPermission = StrCast(targetDoc[userKey]);
+ // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
<div key={'owner'} className={'container'}>
@@ -544,7 +510,10 @@ export class SharingManager extends React.Component<{}> {
<div key={'me'} className={'container'}>
<span className={'padding'}>Me</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? HierarchyMapping.get(effectiveAcls[0])!.name : '-multiple-'}</div>
+ <div className={`permissions-dropdown-${curUserPermission}`}>
+ {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
+ &nbsp;
+ </div>
</div>
</div>
) : null
@@ -552,29 +521,27 @@ export class SharingManager extends React.Component<{}> {
// 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" });
+ groupListMap.unshift({ title: 'Guest' }); //, { title: "ALL" });
const groupListContents = groupListMap.map(group => {
- const groupKey = `acl-${StrCast(group.title)}`;
- const uniform = docs
- .map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
- .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey]));
- const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
+ let groupKey = `acl-${StrCast(group.title)}`;
+ const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
+ const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
return !permissions ? null : (
- <div key={groupKey} className={'container'}>
+ <div key={groupKey} className={'container'} style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}>
<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">
+ &nbsp;
+ {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.Instance.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : 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-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
+ {this.sharingOptions(uniform, group.title === 'Guest')}
</select>
) : (
- <div className={'permissions-dropdown'}>{permissions}</div>
+ <div className={`permissions-dropdown-${permissions}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -583,22 +550,28 @@ export class SharingManager extends React.Component<{}> {
return (
<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'}>
+ <div
+ className="sharing-contents"
+ style={{
+ background: SettingsManager.Instance.userBackgroundColor,
+ color: StrCast(Doc.UserDoc().userColor),
+ }}>
+ <p className="share-title" style={{ color: StrCast(Doc.UserDoc().userColor) }}>
+ <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}>
+ <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
+ </div>
<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 className="share-copy-link">
+ <Button type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
</div>
- {/* {this.linkVisible ?
- <div>
- {this.sharingUrl}
- </div> :
- (null)} */}
- {
+ <div className="close-button">
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
+ </div>
+ {admin ? (
<div className="share-container">
- <div className="share-setup">
+ <div className="share-setup" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
className="user-search"
placeholder="Enter user or group name..."
@@ -610,17 +583,34 @@ export class SharingManager extends React.Component<{}> {
onChange={this.handleUsersChange}
value={this.selectedUsers}
styles={{
+ control: () => ({
+ display: 'inline-flex',
+ width: '100%',
+ }),
indicatorSeparator: () => ({
+ display: 'inline-flex',
visibility: 'hidden',
}),
+ indicatorsContainer: () => ({
+ display: 'inline-flex',
+ textDecorationColor: 'black',
+ }),
+ valueContainer: () => ({
+ display: 'inline-flex',
+ fontStyle: StrCast(Doc.UserDoc().userColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ width: '100%',
+ }),
}}
/>
- <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 className="permissions-select">
+ <select className={`permissions-dropdown-${this.permissions}`} onChange={this.handlePermissionsChange} value={this.permissions}>
+ {this.sharingOptions(true)}
+ </select>
+ </div>
+ <div className="share-button">
+ <Button text={'SHARE'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.share} />
+ </div>
</div>
<div className="sort-checkboxes">
<input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label>
@@ -630,36 +620,46 @@ export class SharingManager extends React.Component<{}> {
<div className="acl-container">
{Doc.noviceMode ? null : (
<div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.upgradeNested = !this.upgradeNested))} checked={this.upgradeNested} /> <label>Upgrade Nested </label>
<input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
</div>
)}
</div>
</div>
- }
- <div className="main-container">
+ ) : (
+ <div className="share-container">
+ <div className="acl-container">
+ <div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ </div>
+ </div>
+ </div>
+ )}
+ <div className="main-container" style={{ color: StrCast(Doc.UserDoc().userColor), border: StrCast(Doc.UserDoc().userColor) }}>
<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 className="title-individual">
+ Individuals
+ <IconButton
+ icon={<FontAwesomeIcon icon={this.individualSort === 'ascending' ? 'caret-up' : this.individualSort === 'descending' ? 'caret-down' : 'caret-right'} />}
+ size={Size.XSMALL}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ </div>
</div>
- <div className={'users-list'}>{userListContents}</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 className="title-group">
+ Groups
+ <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
+ <IconButton
+ icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />}
+ size={Size.XSMALL}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ </div>
</div>
<div className={'groups-list'}>{groupListContents}</div>
</div>
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 6fef9d660..9a6719ea5 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,4 +1,6 @@
import { observable, action, runInAction } from 'mobx';
+import { Field } from '../../fields/Doc';
+import { RichTextField } from '../../fields/RichTextField';
import { Without } from '../../Utils';
function getBatchName(target: any, key: string | symbol): string {
@@ -91,11 +93,17 @@ export namespace UndoManager {
let currentBatch: UndoBatch | undefined;
export let batchCounter = observable.box(0);
let undoing = false;
- let tempEvents: UndoEvent[] | undefined = undefined;
+ export let tempEvents: UndoEvent[] | undefined = undefined;
export function AddEvent(event: UndoEvent, value?: any): void {
if (currentBatch && batchCounter.get() && !undoing) {
- console.log(' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + value);
+ console.log(
+ ' '.slice(0, batchCounter.get()) +
+ 'UndoEvent : ' +
+ event.prop +
+ ' = ' +
+ (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value))
+ );
currentBatch.push(event);
tempEvents?.push(event);
}
@@ -187,15 +195,11 @@ export namespace UndoManager {
return false;
});
- export function RunInTempBatch<T>(fn: () => T) {
+ export function StartTempBatch() {
tempEvents = [];
- try {
- const success = runInAction(fn);
- if (!success) UndoManager.UndoTempBatch();
- return success;
- } finally {
- tempEvents = undefined;
- }
+ }
+ export function EndTempBatch<T>(success: boolean) {
+ UndoManager.UndoTempBatch(success);
}
//TODO Make this return the return value
export function RunInBatch<T>(fn: () => T, batchName: string) {
@@ -206,10 +210,11 @@ export namespace UndoManager {
batch.end();
}
}
- export const UndoTempBatch = action(() => {
- if (tempEvents) {
+ export const UndoTempBatch = action((success: any) => {
+ if (tempEvents && !success) {
undoing = true;
for (let i = tempEvents.length - 1; i >= 0; i--) {
+ currentBatch?.includes(tempEvents[i]) && currentBatch.splice(currentBatch.indexOf(tempEvents[i]));
tempEvents[i].undo();
}
undoing = false;
diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss
new file mode 100644
index 000000000..cd6a1d934
--- /dev/null
+++ b/src/client/util/reportManager/ReportManager.scss
@@ -0,0 +1,364 @@
+@import '../../views/global/globalCssVariables';
+
+// header
+
+.report-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .header-btns {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+
+ h2 {
+ margin: 0;
+ padding: 0;
+ font-size: 24px;
+ }
+}
+
+.report-header-vertical {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 4px;
+
+ h2 {
+ margin: 0;
+ padding: 0;
+ padding-bottom: 8px;
+ font-size: 24px;
+ }
+}
+
+// Report
+
+.report-issue {
+ width: 450px;
+ min-width: 300px;
+ padding: 16px;
+ padding-top: 32px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ // background-color: #ffffff;
+ text-align: left;
+ position: relative;
+
+ .report-label {
+ font-size: 14px;
+ font-weight: 400;
+ }
+
+ .report-section {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .report-textarea {
+ border: none;
+ outline: none;
+ width: 100%;
+ height: 80px;
+ padding: 8px;
+ resize: vertical;
+ background: transparent;
+ transition: border 0.3s ease;
+ }
+
+ .report-selects {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 16px;
+ background-color: transparent;
+
+ .report-select {
+ padding: 8px;
+ background-color: transparent;
+
+ .report-opt {
+ padding: 8px;
+ }
+ }
+ }
+}
+
+.report-input {
+ border: none;
+ outline: none;
+ border-bottom: 1px solid;
+ padding: 8px;
+ padding-left: 0;
+ transition: all 0.2s ease;
+ background: transparent;
+
+ &:hover {
+ // border-bottom-color: $text-gray;
+ }
+ &:focus {
+ // border-bottom-color: #4476f7;
+ }
+}
+
+// View issues
+
+.view-issues {
+ width: 75vw;
+ min-width: 500px;
+ display: flex;
+ gap: 16px;
+ height: 100%;
+ overflow-x: auto;
+
+ video::-webkit-media-controls {
+ display: flex !important;
+ }
+
+ .left {
+ flex: 1;
+ height: 100%;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ text-align: left;
+ position: relative;
+
+ .issues {
+ padding-top: 24px;
+ position: relative;
+ flex-grow: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+ }
+
+ .right {
+ position: relative;
+ flex: 1;
+ padding: 16px;
+ min-width: 300px;
+ height: 100%;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+}
+
+// Issue
+
+.issue-card {
+ cursor: pointer;
+ padding: 16px;
+ border: 1px solid;
+ transition: all 0.1s ease;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+
+ .issue-top {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding-bottom: 8px;
+ }
+
+ .issue-label {
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 400;
+ padding: 0;
+ margin: 0;
+ }
+
+ .issue-title {
+ font-size: 16px;
+ font-weight: 500;
+ padding: 0;
+ margin: 0;
+ }
+}
+
+// Dropzone
+
+.dropzone {
+ padding: 2rem;
+ border-radius: 0.5rem;
+ border: 2px dashed;
+
+ .dropzone-instructions {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+
+ p {
+ text-align: center;
+ }
+ }
+}
+
+.file-list {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ font-size: 14px;
+ width: 100%;
+ overflow-x: auto;
+ list-style-type: none;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+
+ .file-name {
+ padding: 8px 12px;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ white-space: nowrap;
+ }
+}
+
+// Detailed issue view
+
+.issue-view {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ text-align: left;
+ position: relative;
+ overflow: auto;
+
+ .issue-label {
+ .issue-link {
+ cursor: pointer;
+ color: #4476f7;
+ }
+ }
+
+ .issue-title {
+ font-size: 24px;
+ margin: 0;
+ padding: 0;
+ }
+
+ .issue-date {
+ font-size: 14px;
+ }
+
+ .issue-content {
+ font-size: 14px;
+ }
+}
+
+// tags flex lists
+
+.issues-filters {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .issues-filter {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ white-space: nowrap;
+ overflow-x: auto;
+ }
+}
+
+.issue-tags {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ white-space: nowrap;
+ overflow-x: auto;
+}
+
+// Media previews
+
+.report-media-wrapper {
+ position: relative;
+ cursor: pointer;
+
+ .close-btn {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ opacity: 0;
+ }
+
+ .report-media-content {
+ position: relative;
+ display: inline block;
+
+ video::-webkit-media-controls {
+ display: flex !important;
+ }
+ }
+
+ .report-media-content::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5); /* Adjust the opacity as desired */
+ opacity: 0;
+ transition: opacity 0.3s ease; /* Transition for smooth effect */
+ pointer-events: none;
+
+ video::-webkit-media-controls {
+ pointer-events: all;
+ }
+ }
+
+ &:hover {
+ .report-media-content::after {
+ opacity: 1;
+ }
+
+ .close-btn {
+ opacity: 1;
+ }
+ }
+}
+
+.report-audio-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+@media (max-width: 1100px) {
+ .report-header {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 2rem;
+ }
+}
+
+// Tag styling
+
+.report-tag {
+ box-sizing: border-box;
+ padding: 4px 10px;
+ font-size: 10px;
+ border-radius: 32px;
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
+}
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
new file mode 100644
index 000000000..7aad0f2b1
--- /dev/null
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -0,0 +1,406 @@
+import * as React from 'react';
+import v4 = require('uuid/v4');
+import '.././SettingsManager.scss';
+import './ReportManager.scss';
+import Dropzone from 'react-dropzone';
+import ReactLoading from 'react-loading';
+import { action, observable } from 'mobx';
+import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
+import { CgClose } from 'react-icons/cg';
+import { AiOutlineUpload } from 'react-icons/ai';
+import { HiOutlineArrowLeft } from 'react-icons/hi';
+import { Issue } from './reportManagerSchema';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../fields/Doc';
+import { MainViewModal } from '../../views/MainViewModal';
+import { Octokit } from '@octokit/core';
+import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
+import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
+import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './ReportManagerComponents';
+import { StrCast } from '../../../fields/Types';
+import { MdRefresh } from 'react-icons/md';
+import { SettingsManager } from '../SettingsManager';
+const higflyout = require('@hig/flyout');
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+/**
+ * Class for reporting and viewing Github issues within the app.
+ */
+@observer
+export class ReportManager extends React.Component<{}> {
+ public static Instance: ReportManager;
+ @observable private isOpen = false;
+
+ @observable private query = '';
+ @action private setQuery = (q: string) => {
+ this.query = q;
+ };
+
+ private octokit: Octokit;
+
+ @observable viewState: ViewState = ViewState.VIEW;
+ @action private setViewState = (state: ViewState) => {
+ this.viewState = state;
+ };
+ @observable submitting: boolean = false;
+ @action private setSubmitting = (submitting: boolean) => {
+ this.submitting = submitting;
+ };
+
+ @observable fetchingIssues: boolean = false;
+ @action private setFetchingIssues = (fetching: boolean) => {
+ this.fetchingIssues = fetching;
+ };
+
+ @observable
+ public shownIssues: Issue[] = [];
+ @action setShownIssues = action((issues: Issue[]) => {
+ this.shownIssues = issues;
+ });
+
+ @observable
+ public priorityFilter: Priority | null = null;
+ @action setPriorityFilter = action((priority: Priority | null) => {
+ this.priorityFilter = priority;
+ });
+
+ @observable
+ public bugFilter: BugType | null = null;
+ @action setBugFilter = action((bug: BugType | null) => {
+ this.bugFilter = bug;
+ });
+
+ @observable selectedIssue: Issue | undefined = undefined;
+ @action setSelectedIssue = action((issue: Issue | undefined) => {
+ this.selectedIssue = issue;
+ });
+
+ @observable rightExpanded: boolean = false;
+ @action setRightExpanded = action((expanded: boolean) => {
+ this.rightExpanded = expanded;
+ });
+
+ // Form state
+ @observable private formData: ReportForm = emptyReportForm;
+ @action setFormData = action((newData: ReportForm) => {
+ this.formData = newData;
+ });
+
+ public close = action(() => (this.isOpen = false));
+ public open = action(async () => {
+ this.isOpen = true;
+ if (this.shownIssues.length === 0) {
+ this.updateIssues();
+ }
+ });
+
+ @action updateIssues = action(async () => {
+ this.setFetchingIssues(true);
+ try {
+ const issues = (await getAllIssues(this.octokit)) as Issue[];
+ this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
+ } catch (err) {
+ console.log(err);
+ }
+ this.setFetchingIssues(false);
+ });
+
+ constructor(props: {}) {
+ super(props);
+ ReportManager.Instance = this;
+
+ // initializing Github connection
+ this.octokit = new Octokit({
+ auth: process.env.GITHUB_ACCESS_TOKEN,
+ });
+ }
+
+ /**
+ * Sends a request to Github to report a new issue with the form data.
+ * @returns nothing
+ */
+ public async reportIssue(): Promise<void> {
+ if (this.formData.title === '' || this.formData.description === '') {
+ alert('Please fill out all required fields to report an issue.');
+ return;
+ }
+ this.setSubmitting(true);
+ let formattedLinks: string[] = [];
+ if (this.formData.mediaFiles.length > 0) {
+ const links = await uploadFilesToServer(this.formData.mediaFiles);
+ if (links) {
+ formattedLinks = links;
+ }
+ }
+
+ const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
+ owner: 'brown-dash',
+ repo: 'Dash-Web',
+ title: formatTitle(this.formData.title, Doc.CurrentUserEmail),
+ body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`,
+ labels: ['from-dash-app', this.formData.type, this.formData.priority],
+ });
+
+ // 201 status means success
+ if (req.status !== 201) {
+ alert('Error creating issue on github.');
+ } else {
+ await this.updateIssues();
+ alert('Successfully submitted issue.');
+ }
+ this.setFormData(emptyReportForm);
+ this.setSubmitting(false);
+ }
+
+ /**
+ * Handles file upload.
+ *
+ * @param files uploaded files
+ */
+ private onDrop = (files: File[]) => {
+ this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...files.map(file => ({ _id: v4(), file }))] });
+ };
+
+ /**
+ * Gets a JSX element to render a media preview
+ * @param fileData file data
+ * @returns JSX element of a piece of media (image, video, audio)
+ */
+ private getMediaPreview = (fileData: FileData): JSX.Element => {
+ const file = fileData.file;
+ const mimeType = file.type;
+ const preview = URL.createObjectURL(file);
+
+ if (mimeType.startsWith('image/')) {
+ return (
+ <div key={fileData._id} className="report-media-wrapper">
+ <div className="report-media-content">
+ <img height={100} alt={`Preview of ${file.name}`} src={preview} style={{ display: 'block' }} />
+ </div>
+ <div className="close-btn">
+ <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
+ </div>
+ </div>
+ );
+ } else if (mimeType.startsWith('video/')) {
+ return (
+ <div key={fileData._id} className="report-media-wrapper">
+ <div className="report-media-content">
+ <video className="report-default-video" controls style={{ height: '100px', width: 'auto', display: 'block' }}>
+ <source src={preview} type="video/mp4" />
+ Your browser does not support the video tag.
+ </video>
+ </div>
+ <div className="close-btn">
+ <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
+ </div>
+ </div>
+ );
+ } else if (mimeType.startsWith('audio/')) {
+ return (
+ <div key={fileData._id} className="report-audio-wrapper">
+ <audio src={preview} controls />
+ <div className="close-btn">
+ <IconButton icon={<BsX color="#ffffff" />} onClick={() => this.setFormData({ ...this.formData, mediaFiles: this.formData.mediaFiles.filter(f => f._id !== fileData._id) })} />
+ </div>
+ </div>
+ );
+ }
+ return <></>;
+ };
+
+ /**
+ * @returns the component that dispays all issues
+ */
+ private viewIssuesComponent = () => {
+ const darkMode = isDarkMode(SettingsManager.Instance.userBackgroundColor);
+ const colors = darkMode ? darkColors : lightColors;
+
+ return (
+ <div className="view-issues" style={{ backgroundColor: SettingsManager.Instance.userBackgroundColor, color: colors.text }}>
+ <div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}>
+ <div className="report-header">
+ <h2 style={{ color: colors.text }}>Open Issues</h2>
+ <div className="header-btns">
+ <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="refresh" icon={<MdRefresh size="16px" />} onClick={this.updateIssues} />
+ <Button
+ type={Type.TERT}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ text="Report Issue"
+ onClick={() => {
+ this.setViewState(ViewState.CREATE);
+ }}
+ />
+ </div>
+ </div>
+ <FormInput value={this.query} placeholder="Filter by query..." onChange={this.setQuery} />
+ <div className="issues-filters">
+ <Filter items={Object.values(Priority)} activeValue={this.priorityFilter} setActiveValue={p => this.setPriorityFilter(p)} />
+ <Filter items={Object.values(BugType)} activeValue={this.bugFilter} setActiveValue={b => this.setBugFilter(b)} />
+ </div>
+ <div className="issues">
+ {this.fetchingIssues ? (
+ <div style={{ flexGrow: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+ <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userColor)} width={50} height={50} />
+ </div>
+ ) : (
+ this.shownIssues
+ .filter(issue => issue.title.toLowerCase().includes(this.query))
+ .filter(issue => passesTagFilter(issue, this.priorityFilter, this.bugFilter))
+ .map(issue => (
+ <IssueCard
+ key={issue.number}
+ issue={issue}
+ onSelect={() => {
+ this.setSelectedIssue(issue);
+ }}
+ />
+ ))
+ )}
+ </div>
+ </div>
+ <div className="right">{this.selectedIssue ? <IssueView key={this.selectedIssue.number} issue={this.selectedIssue} /> : <div>No issue selected</div>} </div>
+ <div style={{ position: 'absolute', top: '8px', right: '8px', display: 'flex', gap: '16px' }}>
+ <IconButton
+ color={StrCast(Doc.UserDoc().userColor)}
+ tooltip={this.rightExpanded ? 'Minimize right side' : 'Expand right side'}
+ icon={this.rightExpanded ? <BsArrowsAngleContract size="16px" /> : <BsArrowsAngleExpand size="16px" />}
+ onClick={e => {
+ e.stopPropagation();
+ this.setRightExpanded(!this.rightExpanded);
+ }}
+ />
+ <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
+ </div>
+ </div>
+ );
+ };
+
+ /**
+ * @returns the form component for submitting issues
+ */
+ private reportIssueComponent = () => {
+ const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
+ const colors = darkMode ? darkColors : lightColors;
+
+ return (
+ <div className="report-issue" style={{ color: colors.text }}>
+ <div className="report-header-vertical">
+ <Button
+ type={Type.PRIM}
+ color={StrCast(Doc.UserDoc().userColor)}
+ text="back to view"
+ icon={<HiOutlineArrowLeft />}
+ iconPlacement="left"
+ onClick={() => {
+ this.setViewState(ViewState.VIEW);
+ }}
+ />
+ <h2>Report an Issue</h2>
+ </div>
+ <div className="report-section">
+ <label className="report-label">Please provide a title for the bug</label>
+ <FormInput value={this.formData.title} placeholder="Title..." onChange={val => this.setFormData({ ...this.formData, title: val })} />
+ </div>
+ <div className="report-section">
+ <label className="report-label">Please leave a description for the bug and how it can be recreated</label>
+ <FormTextArea value={this.formData.description} placeholder="Description..." onChange={val => this.setFormData({ ...this.formData, description: val })} />
+ </div>
+ <div className="report-selects">
+ <Dropdown
+ color={StrCast(Doc.UserDoc().userColor)}
+ formLabel={'Type'}
+ items={bugDropdownItems}
+ selectedVal={this.formData.type}
+ setSelectedVal={val => {
+ if (typeof val === 'string') this.setFormData({ ...this.formData, type: val as BugType });
+ }}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ fillWidth
+ />
+ <Dropdown
+ color={StrCast(Doc.UserDoc().userColor)}
+ formLabel={'Priority'}
+ items={priorityDropdownItems}
+ selectedVal={this.formData.priority}
+ setSelectedVal={val => {
+ if (typeof val === 'string') this.setFormData({ ...this.formData, priority: val as Priority });
+ }}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ fillWidth
+ />
+ </div>
+ <Dropzone
+ onDrop={this.onDrop}
+ accept={{
+ 'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
+ 'video/*': ['.mp4', '.mpeg', '.webm', '.mov'],
+ 'audio/mpeg': ['.mp3'],
+ 'audio/wav': ['.wav'],
+ 'audio/ogg': ['.ogg'],
+ }}>
+ {({ getRootProps, getInputProps }) => (
+ <div {...getRootProps({ className: 'dropzone' })} style={{ borderColor: isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border }}>
+ <input {...getInputProps()} />
+ <div className="dropzone-instructions">
+ <AiOutlineUpload size={25} />
+ <p>Drop or select media that shows the bug (optional)</p>
+ </div>
+ </div>
+ )}
+ </Dropzone>
+ {this.formData.mediaFiles.length > 0 && <ul className="file-list">{this.formData.mediaFiles.map(file => this.getMediaPreview(file))}</ul>}
+ {this.submitting ? (
+ <Button
+ text="Submit"
+ type={Type.TERT}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+ iconPlacement="right"
+ onClick={() => {
+ this.reportIssue();
+ }}
+ />
+ ) : (
+ <Button
+ text="Submit"
+ type={Type.TERT}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ onClick={() => {
+ this.reportIssue();
+ }}
+ />
+ )}
+ <div style={{ position: 'absolute', top: '4px', right: '4px' }}>
+ <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size={'16px'} />} onClick={this.close} />
+ </div>
+ </div>
+ );
+ };
+
+ /**
+ * @returns the component rendered to the modal
+ */
+ private reportComponent = () => {
+ if (this.viewState === ViewState.VIEW) {
+ return this.viewIssuesComponent();
+ } else {
+ return this.reportIssueComponent();
+ }
+ };
+
+ render() {
+ return (
+ <MainViewModal
+ contents={this.reportComponent()}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ closeOnExternalClick={this.close}
+ dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: StrCast(Doc.UserDoc().userBackgroundColor), borderRadius: '8px' }}
+ />
+ );
+ }
+}
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
new file mode 100644
index 000000000..e870c073d
--- /dev/null
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -0,0 +1,381 @@
+import * as React from 'react';
+import { Issue } from './reportManagerSchema';
+import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
+import ReactMarkdown from 'react-markdown';
+import rehypeRaw from 'rehype-raw';
+import remarkGfm from 'remark-gfm';
+import { StrCast } from '../../../fields/Types';
+import { Doc } from '../../../fields/Doc';
+
+/**
+ * Mini helper components for the report component.
+ */
+
+interface FilterProps<T> {
+ items: T[];
+ activeValue: T | null;
+ setActiveValue: (val: T | null) => void;
+}
+
+// filter ui for issues (horizontal list of tags)
+export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => {
+ // establishing theme
+ const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
+ const colors = darkMode ? darkColors : lightColors;
+ const isTagDarkMode = isDarkMode(StrCast(Doc.UserDoc().userColor));
+ const activeTagTextColor = isTagDarkMode ? darkColors.text : lightColors.text;
+
+ return (
+ <div className="issues-filter">
+ <Tag
+ text={'All'}
+ onClick={() => {
+ setActiveValue(null);
+ }}
+ fontSize="12px"
+ backgroundColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
+ color={activeValue === null ? activeTagTextColor : colors.textGrey}
+ borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border}
+ border
+ />
+ {items.map(item => {
+ return (
+ <Tag
+ key={item}
+ text={item}
+ onClick={() => {
+ setActiveValue(item);
+ }}
+ fontSize="12px"
+ backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
+ color={activeValue === item ? activeTagTextColor : colors.textGrey}
+ border
+ borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
+ />
+ );
+ })}
+ </div>
+ );
+};
+
+interface IssueCardProps {
+ issue: Issue;
+ onSelect: () => void;
+}
+
+// Component for the issue cards list on the left
+export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+ const [textColor, setTextColor] = React.useState('');
+ const [bgColor, setBgColor] = React.useState('transparent');
+ const [borderColor, setBorderColor] = React.useState('transparent');
+
+ const resetColors = () => {
+ const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
+ const colors = darkMode ? darkColors : lightColors;
+ setTextColor(colors.text);
+ setBorderColor(colors.border);
+ setBgColor('transparent');
+ };
+
+ const handlePointerOver = () => {
+ const darkMode = isDarkMode(StrCast(Doc.UserDoc().userColor));
+ setTextColor(darkMode ? darkColors.text : lightColors.text);
+ setBorderColor(StrCast(Doc.UserDoc().userColor));
+ setBgColor(StrCast(Doc.UserDoc().userColor));
+ };
+
+ React.useEffect(() => {
+ resetColors();
+ }, []);
+
+ return (
+ <div className="issue-card" onClick={onSelect} style={{ color: textColor, backgroundColor: bgColor, borderColor: borderColor }} onPointerOver={handlePointerOver} onPointerOut={resetColors}>
+ <div className="issue-top">
+ <label className="issue-label">#{issue.number}</label>
+ <div className="issue-tags">
+ {issue.labels.map(label => {
+ const labelString = typeof label === 'string' ? label : label.name ?? '';
+ const colors = getLabelColors(labelString);
+ return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} />;
+ })}
+ </div>
+ </div>
+ <h3 className="issue-title">{issue.title}</h3>
+ </div>
+ );
+};
+
+interface IssueViewProps {
+ issue: Issue;
+}
+
+// Detailed issue view that displays on the right
+export const IssueView = ({ issue }: IssueViewProps) => {
+ const [issueBody, setIssueBody] = React.useState('');
+
+ // Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
+ const parseBody = async (body: string) => {
+ const imgTagRegex = /<img\b[^>]*\/?>/;
+ const videoTagRegex = /<video\b[^>]*\/?>/;
+ const audioTagRegex = /<audio\b[^>]*\/?>/;
+
+ const fileRegex = /https:\/\/browndash\.com\/files/;
+ const localRegex = /http:\/\/localhost:1050\/files/;
+ const parts = body.split('\n');
+
+ const modifiedParts = await Promise.all(
+ parts.map(async part => {
+ if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) {
+ return `\n${await parseFileTag(part)}\n`;
+ } else if (fileRegex.test(part)) {
+ const tag = await parseDashFiles(part);
+ return tag;
+ } else if (localRegex.test(part)) {
+ const tag = await parseLocalFiles(part);
+ return tag;
+ } else {
+ return part;
+ }
+ })
+ );
+
+ setIssueBody(modifiedParts.join('\n'));
+ };
+
+ // Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag
+ const parseFileTag = async (tag: string): Promise<string> => {
+ const regex = /src="([^"]+)"/;
+ let url = '';
+ const match = tag.match(regex);
+ if (!match) return tag;
+ url = match[1];
+ if (!url) return tag;
+
+ const mimeType = url.split('.').pop();
+ if (!mimeType) return tag;
+
+ switch (mimeType) {
+ // image
+ case '.jpg':
+ case '.png':
+ case '.jpeg':
+ case '.gif':
+ return await getDisplayedFile(url, 'image');
+ // video
+ case '.mp4':
+ case '.mpeg':
+ case '.webm':
+ case '.mov':
+ return await getDisplayedFile(url, 'video');
+ //audio
+ case '.mp3':
+ case '.wav':
+ case '.ogg':
+ return await getDisplayedFile(url, 'audio');
+ }
+ return tag;
+ };
+
+ // Returns the corresponding HTML tag for a src url
+ const parseDashFiles = async (url: string) => {
+ const dashImgRegex = /https:\/\/browndash\.com\/files[/\\]images/;
+ const dashVideoRegex = /https:\/\/browndash\.com\/files[/\\]videos/;
+ const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
+
+ if (dashImgRegex.test(url)) {
+ return await getDisplayedFile(url, 'image');
+ } else if (dashVideoRegex.test(url)) {
+ return await getDisplayedFile(url, 'video');
+ } else if (dashAudioRegex.test(url)) {
+ return await getDisplayedFile(url, 'audio');
+ } else {
+ return url;
+ }
+ };
+
+ // Returns the corresponding HTML tag for a src url
+ const parseLocalFiles = async (url: string) => {
+ const imgRegex = /http:\/\/localhost:1050\/files[/\\]images/;
+ const dashVideoRegex = /http:\/\/localhost:1050\.com\/files[/\\]videos/;
+ const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/;
+
+ if (imgRegex.test(url)) {
+ return await getDisplayedFile(url, 'image');
+ } else if (dashVideoRegex.test(url)) {
+ return await getDisplayedFile(url, 'video');
+ } else if (dashAudioRegex.test(url)) {
+ return await getDisplayedFile(url, 'audio');
+ } else {
+ return url;
+ }
+ };
+
+ const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
+ switch (fileType) {
+ case 'image':
+ const imgValid = await isImgValid(url);
+ if (!imgValid) return `\n${url} (This image could not be loaded)\n`;
+ return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
+ case 'video':
+ const videoValid = await isVideoValid(url);
+ if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
+ return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
+ case 'audio':
+ const audioValid = await isAudioValid(url);
+ if (!audioValid) return `\n${url} (This audio could not be loaded)\n`;
+ return `\n${url}\n<audio src=${url} controls />\n`;
+ }
+ };
+
+ // Loads an image and returns a promise that resolves as whether the image is valid or not
+ const isImgValid = (src: string): Promise<boolean> => {
+ const imgElement = document.createElement('img');
+ const validPromise: Promise<boolean> = new Promise(resolve => {
+ imgElement.addEventListener('load', () => resolve(true));
+ imgElement.addEventListener('error', () => resolve(false));
+ });
+ imgElement.src = src;
+ return validPromise;
+ };
+
+ // Loads a video and returns a promise that resolves as whether the video is valid or not
+ const isVideoValid = (src: string): Promise<boolean> => {
+ const videoElement = document.createElement('video');
+ const validPromise: Promise<boolean> = new Promise(resolve => {
+ videoElement.addEventListener('loadeddata', () => resolve(true));
+ videoElement.addEventListener('error', () => resolve(false));
+ });
+ videoElement.src = src;
+ return validPromise;
+ };
+
+ // Loads audio and returns a promise that resolves as whether the audio is valid or not
+ const isAudioValid = (src: string): Promise<boolean> => {
+ const audioElement = document.createElement('audio');
+ const validPromise: Promise<boolean> = new Promise(resolve => {
+ audioElement.addEventListener('loadeddata', () => resolve(true));
+ audioElement.addEventListener('error', () => resolve(false));
+ });
+ audioElement.src = src;
+ return validPromise;
+ };
+
+ // Called on mount to parse the body
+ React.useEffect(() => {
+ setIssueBody('Loading...');
+ parseBody((issue.body as string) ?? '');
+ }, [issue]);
+
+ return (
+ <div className="issue-view">
+ <span className="issue-label">
+ Issue{' '}
+ <a className="issue-link" href={issue.html_url} target="_blank">
+ #{issue.number}
+ </a>
+ </span>
+ <h2 className="issue-title">{issue.title}</h2>
+ <div className="issue-date">
+ Opened on {new Date(issue.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} {issue.user?.login && `by ${issue.user?.login}`}
+ </div>
+ {issue.labels.length > 0 && (
+ <div>
+ <div className="issue-tags">
+ {issue.labels.map(label => {
+ const labelString = typeof label === 'string' ? label : label.name ?? '';
+ const colors = getLabelColors(labelString);
+ return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} fontSize="12px" />;
+ })}
+ </div>
+ </div>
+ )}
+ <ReactMarkdown children={issueBody} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
+ </div>
+ );
+};
+
+interface TagProps {
+ text: string;
+ fontSize?: string;
+ color?: string;
+ backgroundColor?: string;
+ borderColor?: string;
+ border?: boolean;
+ onClick?: () => void;
+}
+
+// Small tag for labels of the issue
+export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+ return (
+ <div
+ onClick={onClick ?? (() => {})}
+ className="report-tag"
+ style={{ color: color ?? '#ffffff', backgroundColor: backgroundColor ?? '#347bff', cursor: onClick ? 'pointer' : 'auto', fontSize: fontSize ?? '10px', border: border ? '1px solid' : 'none', borderColor: borderColor ?? '#94a3b8' }}>
+ {text}
+ </div>
+ );
+};
+
+interface FormInputProps {
+ value: string;
+ placeholder: string;
+ onChange: (val: string) => void;
+}
+export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
+ const [inputBorderColor, setInputBorderColor] = React.useState('');
+
+ return (
+ <input
+ className="report-input"
+ style={{ borderBottom: `1px solid ${inputBorderColor}` }}
+ value={value}
+ type="text"
+ placeholder={placeholder}
+ onChange={e => onChange(e.target.value)}
+ required
+ onPointerOver={() => {
+ if (inputBorderColor === dashBlue) return;
+ setInputBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.textGrey : lightColors.textGrey);
+ }}
+ onPointerOut={() => {
+ if (inputBorderColor === dashBlue) return;
+ setInputBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
+ }}
+ onFocus={() => {
+ setInputBorderColor(dashBlue);
+ }}
+ onBlur={() => {
+ setInputBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
+ }}
+ />
+ );
+};
+
+export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => {
+ const [textAreaBorderColor, setTextAreaBorderColor] = React.useState('');
+
+ return (
+ <textarea
+ className="report-textarea"
+ value={value}
+ placeholder={placeholder}
+ onChange={e => onChange(e.target.value)}
+ required
+ style={{ border: `1px solid ${textAreaBorderColor}` }}
+ onPointerOver={() => {
+ if (textAreaBorderColor === dashBlue) return;
+ setTextAreaBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.textGrey : lightColors.textGrey);
+ }}
+ onPointerOut={() => {
+ if (textAreaBorderColor === dashBlue) return;
+ setTextAreaBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
+ }}
+ onFocus={() => {
+ setTextAreaBorderColor(dashBlue);
+ }}
+ onBlur={() => {
+ setTextAreaBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
+ }}
+ />
+ );
+};
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
new file mode 100644
index 000000000..9a1c7c3e9
--- /dev/null
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -0,0 +1,877 @@
+/**
+ * Issue interface schema from Github.
+ */
+export interface Issue {
+ active_lock_reason?: null | string;
+ assignee: null | PurpleSimpleUser;
+ assignees?: AssigneeElement[] | null;
+ /**
+ * How the author is associated with the repository.
+ */
+ author_association: AuthorAssociation;
+ /**
+ * Contents of the issue
+ */
+ body?: null | string;
+ body_html?: string;
+ body_text?: string;
+ closed_at: Date | null;
+ closed_by?: null | FluffySimpleUser;
+ comments: number;
+ comments_url: string;
+ created_at: Date;
+ draft?: boolean;
+ events_url: string;
+ html_url: string;
+ id: number;
+ /**
+ * Labels to associate with this issue; pass one or more label names to replace the set of
+ * labels on this issue; send an empty array to clear all labels from the issue; note that
+ * the labels are silently dropped for users without push access to the repository
+ */
+ labels: Array<LabelObject | string>;
+ labels_url: string;
+ locked: boolean;
+ milestone: null | Milestone;
+ node_id: string;
+ /**
+ * Number uniquely identifying the issue within its repository
+ */
+ number: number;
+ performed_via_github_app?: null | GitHubApp;
+ pull_request?: PullRequest;
+ reactions?: ReactionRollup;
+ /**
+ * A repository on GitHub.
+ */
+ repository?: Repository;
+ repository_url: string;
+ /**
+ * State of the issue; either 'open' or 'closed'
+ */
+ state: string;
+ /**
+ * The reason for the current state
+ */
+ state_reason?: StateReason | null;
+ timeline_url?: string;
+ /**
+ * Title of the issue
+ */
+ title: string;
+ updated_at: Date;
+ /**
+ * URL for the issue
+ */
+ url: string;
+ user: null | TentacledSimpleUser;
+ [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface PurpleSimpleUser {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface AssigneeElement {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
+
+/**
+ * How the author is associated with the repository.
+ */
+export enum AuthorAssociation {
+ Collaborator = 'COLLABORATOR',
+ Contributor = 'CONTRIBUTOR',
+ FirstTimeContributor = 'FIRST_TIME_CONTRIBUTOR',
+ FirstTimer = 'FIRST_TIMER',
+ Mannequin = 'MANNEQUIN',
+ Member = 'MEMBER',
+ None = 'NONE',
+ Owner = 'OWNER',
+}
+
+/**
+ * A GitHub user.
+ */
+export interface FluffySimpleUser {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
+
+export interface LabelObject {
+ color?: null | string;
+ default?: boolean;
+ description?: null | string;
+ id?: number;
+ name?: string;
+ node_id?: string;
+ url?: string;
+ [property: string]: any;
+}
+
+/**
+ * A collection of related issues and pull requests.
+ */
+export interface Milestone {
+ closed_at: Date | null;
+ closed_issues: number;
+ created_at: Date;
+ creator: null | MilestoneSimpleUser;
+ description: null | string;
+ due_on: Date | null;
+ html_url: string;
+ id: number;
+ labels_url: string;
+ node_id: string;
+ /**
+ * The number of the milestone.
+ */
+ number: number;
+ open_issues: number;
+ /**
+ * The state of the milestone.
+ */
+ state: State;
+ /**
+ * The title of the milestone.
+ */
+ title: string;
+ updated_at: Date;
+ url: string;
+ [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface MilestoneSimpleUser {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
+
+/**
+ * The state of the milestone.
+ */
+export enum State {
+ Closed = 'closed',
+ Open = 'open',
+}
+
+/**
+ * GitHub apps are a new way to extend GitHub. They can be installed directly on
+ * organizations and user accounts and granted access to specific repositories. They come
+ * with granular permissions and built-in webhooks. GitHub apps are first class actors
+ * within GitHub.
+ */
+export interface GitHubApp {
+ client_id?: string;
+ client_secret?: string;
+ created_at: Date;
+ description: null | string;
+ /**
+ * The list of events for the GitHub app
+ */
+ events: string[];
+ external_url: string;
+ html_url: string;
+ /**
+ * Unique identifier of the GitHub app
+ */
+ id: number;
+ /**
+ * The number of installations associated with the GitHub app
+ */
+ installations_count?: number;
+ /**
+ * The name of the GitHub app
+ */
+ name: string;
+ node_id: string;
+ owner: null | GitHubAppSimpleUser;
+ pem?: string;
+ /**
+ * The set of permissions for the GitHub app
+ */
+ permissions: GitHubAppPermissions;
+ /**
+ * The slug name of the GitHub app
+ */
+ slug?: string;
+ updated_at: Date;
+ webhook_secret?: null | string;
+ [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface GitHubAppSimpleUser {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
+
+/**
+ * The set of permissions for the GitHub app
+ */
+export interface GitHubAppPermissions {
+ checks?: string;
+ contents?: string;
+ deployments?: string;
+ issues?: string;
+ metadata?: string;
+}
+
+export interface PullRequest {
+ diff_url: null | string;
+ html_url: null | string;
+ merged_at?: Date | null;
+ patch_url: null | string;
+ url: null | string;
+ [property: string]: any;
+}
+
+export interface ReactionRollup {
+ '+1': number;
+ '-1': number;
+ confused: number;
+ eyes: number;
+ heart: number;
+ hooray: number;
+ laugh: number;
+ rocket: number;
+ total_count: number;
+ url: string;
+ [property: string]: any;
+}
+
+/**
+ * A repository on GitHub.
+ */
+export interface Repository {
+ /**
+ * Whether to allow Auto-merge to be used on pull requests.
+ */
+ allow_auto_merge?: boolean;
+ /**
+ * Whether to allow forking this repo
+ */
+ allow_forking?: boolean;
+ /**
+ * Whether to allow merge commits for pull requests.
+ */
+ allow_merge_commit?: boolean;
+ /**
+ * Whether to allow rebase merges for pull requests.
+ */
+ allow_rebase_merge?: boolean;
+ /**
+ * Whether to allow squash merges for pull requests.
+ */
+ allow_squash_merge?: boolean;
+ /**
+ * Whether or not a pull request head branch that is behind its base branch can always be
+ * updated even if it is not required to be up to date before merging.
+ */
+ allow_update_branch?: boolean;
+ /**
+ * Whether anonymous git access is enabled for this repository
+ */
+ anonymous_access_enabled?: boolean;
+ archive_url: string;
+ /**
+ * Whether the repository is archived.
+ */
+ archived: boolean;
+ assignees_url: string;
+ blobs_url: string;
+ branches_url: string;
+ clone_url: string;
+ collaborators_url: string;
+ comments_url: string;
+ commits_url: string;
+ compare_url: string;
+ contents_url: string;
+ contributors_url: string;
+ created_at: Date | null;
+ /**
+ * The default branch of the repository.
+ */
+ default_branch: string;
+ /**
+ * Whether to delete head branches when pull requests are merged
+ */
+ delete_branch_on_merge?: boolean;
+ deployments_url: string;
+ description: null | string;
+ /**
+ * Returns whether or not this repository disabled.
+ */
+ disabled: boolean;
+ downloads_url: string;
+ events_url: string;
+ fork: boolean;
+ forks: number;
+ forks_count: number;
+ forks_url: string;
+ full_name: string;
+ git_commits_url: string;
+ git_refs_url: string;
+ git_tags_url: string;
+ git_url: string;
+ /**
+ * Whether discussions are enabled.
+ */
+ has_discussions?: boolean;
+ /**
+ * Whether downloads are enabled.
+ */
+ has_downloads: boolean;
+ /**
+ * Whether issues are enabled.
+ */
+ has_issues: boolean;
+ has_pages: boolean;
+ /**
+ * Whether projects are enabled.
+ */
+ has_projects: boolean;
+ /**
+ * Whether the wiki is enabled.
+ */
+ has_wiki: boolean;
+ homepage: null | string;
+ hooks_url: string;
+ html_url: string;
+ /**
+ * Unique identifier of the repository
+ */
+ id: number;
+ /**
+ * Whether this repository acts as a template that can be used to generate new repositories.
+ */
+ is_template?: boolean;
+ issue_comment_url: string;
+ issue_events_url: string;
+ issues_url: string;
+ keys_url: string;
+ labels_url: string;
+ language: null | string;
+ languages_url: string;
+ license: null | LicenseSimple;
+ master_branch?: string;
+ /**
+ * The default value for a merge commit message.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `PR_BODY` - default to the pull request's body.
+ * - `BLANK` - default to a blank commit message.
+ */
+ merge_commit_message?: MergeCommitMessage;
+ /**
+ * The default value for a merge commit title.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
+ * request #123 from branch-name).
+ */
+ merge_commit_title?: MergeCommitTitle;
+ merges_url: string;
+ milestones_url: string;
+ mirror_url: null | string;
+ /**
+ * The name of the repository.
+ */
+ name: string;
+ network_count?: number;
+ node_id: string;
+ notifications_url: string;
+ open_issues: number;
+ open_issues_count: number;
+ organization?: null | RepositorySimpleUser;
+ /**
+ * A GitHub user.
+ */
+ owner: OwnerObject;
+ permissions?: RepositoryPermissions;
+ /**
+ * Whether the repository is private or public.
+ */
+ private: boolean;
+ pulls_url: string;
+ pushed_at: Date | null;
+ releases_url: string;
+ /**
+ * The size of the repository. Size is calculated hourly. When a repository is initially
+ * created, the size is 0.
+ */
+ size: number;
+ /**
+ * The default value for a squash merge commit message:
+ *
+ * - `PR_BODY` - default to the pull request's body.
+ * - `COMMIT_MESSAGES` - default to the branch's commit messages.
+ * - `BLANK` - default to a blank commit message.
+ */
+ squash_merge_commit_message?: SquashMergeCommitMessage;
+ /**
+ * The default value for a squash merge commit title:
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
+ * request's title (when more than one commit).
+ */
+ squash_merge_commit_title?: SquashMergeCommitTitle;
+ ssh_url: string;
+ stargazers_count: number;
+ stargazers_url: string;
+ starred_at?: string;
+ statuses_url: string;
+ subscribers_count?: number;
+ subscribers_url: string;
+ subscription_url: string;
+ svn_url: string;
+ tags_url: string;
+ teams_url: string;
+ temp_clone_token?: string;
+ template_repository?: null | TemplateRepository;
+ topics?: string[];
+ trees_url: string;
+ updated_at: Date | null;
+ url: string;
+ /**
+ * Whether a squash merge commit can use the pull request title as default. **This property
+ * has been deprecated. Please use `squash_merge_commit_title` instead.
+ */
+ use_squash_pr_title_as_default?: boolean;
+ /**
+ * The repository visibility: public, private, or internal.
+ */
+ visibility?: string;
+ watchers: number;
+ watchers_count: number;
+ /**
+ * Whether to require contributors to sign off on web-based commits
+ */
+ web_commit_signoff_required?: boolean;
+ [property: string]: any;
+}
+
+/**
+ * License Simple
+ */
+export interface LicenseSimple {
+ html_url?: string;
+ key: string;
+ name: string;
+ node_id: string;
+ spdx_id: null | string;
+ url: null | string;
+ [property: string]: any;
+}
+
+/**
+ * The default value for a merge commit message.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `PR_BODY` - default to the pull request's body.
+ * - `BLANK` - default to a blank commit message.
+ */
+export enum MergeCommitMessage {
+ Blank = 'BLANK',
+ PRBody = 'PR_BODY',
+ PRTitle = 'PR_TITLE',
+}
+
+/**
+ * The default value for a merge commit title.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
+ * request #123 from branch-name).
+ */
+export enum MergeCommitTitle {
+ MergeMessage = 'MERGE_MESSAGE',
+ PRTitle = 'PR_TITLE',
+}
+
+/**
+ * A GitHub user.
+ */
+export interface RepositorySimpleUser {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
+
+/**
+ * A GitHub user.
+ */
+export interface OwnerObject {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
+
+export interface RepositoryPermissions {
+ admin: boolean;
+ maintain?: boolean;
+ pull: boolean;
+ push: boolean;
+ triage?: boolean;
+ [property: string]: any;
+}
+
+/**
+ * The default value for a squash merge commit message:
+ *
+ * - `PR_BODY` - default to the pull request's body.
+ * - `COMMIT_MESSAGES` - default to the branch's commit messages.
+ * - `BLANK` - default to a blank commit message.
+ */
+export enum SquashMergeCommitMessage {
+ Blank = 'BLANK',
+ CommitMessages = 'COMMIT_MESSAGES',
+ PRBody = 'PR_BODY',
+}
+
+/**
+ * The default value for a squash merge commit title:
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
+ * request's title (when more than one commit).
+ */
+export enum SquashMergeCommitTitle {
+ CommitOrPRTitle = 'COMMIT_OR_PR_TITLE',
+ PRTitle = 'PR_TITLE',
+}
+
+export interface TemplateRepository {
+ allow_auto_merge?: boolean;
+ allow_merge_commit?: boolean;
+ allow_rebase_merge?: boolean;
+ allow_squash_merge?: boolean;
+ allow_update_branch?: boolean;
+ archive_url?: string;
+ archived?: boolean;
+ assignees_url?: string;
+ blobs_url?: string;
+ branches_url?: string;
+ clone_url?: string;
+ collaborators_url?: string;
+ comments_url?: string;
+ commits_url?: string;
+ compare_url?: string;
+ contents_url?: string;
+ contributors_url?: string;
+ created_at?: string;
+ default_branch?: string;
+ delete_branch_on_merge?: boolean;
+ deployments_url?: string;
+ description?: string;
+ disabled?: boolean;
+ downloads_url?: string;
+ events_url?: string;
+ fork?: boolean;
+ forks_count?: number;
+ forks_url?: string;
+ full_name?: string;
+ git_commits_url?: string;
+ git_refs_url?: string;
+ git_tags_url?: string;
+ git_url?: string;
+ has_downloads?: boolean;
+ has_issues?: boolean;
+ has_pages?: boolean;
+ has_projects?: boolean;
+ has_wiki?: boolean;
+ homepage?: string;
+ hooks_url?: string;
+ html_url?: string;
+ id?: number;
+ is_template?: boolean;
+ issue_comment_url?: string;
+ issue_events_url?: string;
+ issues_url?: string;
+ keys_url?: string;
+ labels_url?: string;
+ language?: string;
+ languages_url?: string;
+ /**
+ * The default value for a merge commit message.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `PR_BODY` - default to the pull request's body.
+ * - `BLANK` - default to a blank commit message.
+ */
+ merge_commit_message?: MergeCommitMessage;
+ /**
+ * The default value for a merge commit title.
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull
+ * request #123 from branch-name).
+ */
+ merge_commit_title?: MergeCommitTitle;
+ merges_url?: string;
+ milestones_url?: string;
+ mirror_url?: string;
+ name?: string;
+ network_count?: number;
+ node_id?: string;
+ notifications_url?: string;
+ open_issues_count?: number;
+ owner?: Owner;
+ permissions?: TemplateRepositoryPermissions;
+ private?: boolean;
+ pulls_url?: string;
+ pushed_at?: string;
+ releases_url?: string;
+ size?: number;
+ /**
+ * The default value for a squash merge commit message:
+ *
+ * - `PR_BODY` - default to the pull request's body.
+ * - `COMMIT_MESSAGES` - default to the branch's commit messages.
+ * - `BLANK` - default to a blank commit message.
+ */
+ squash_merge_commit_message?: SquashMergeCommitMessage;
+ /**
+ * The default value for a squash merge commit title:
+ *
+ * - `PR_TITLE` - default to the pull request's title.
+ * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull
+ * request's title (when more than one commit).
+ */
+ squash_merge_commit_title?: SquashMergeCommitTitle;
+ ssh_url?: string;
+ stargazers_count?: number;
+ stargazers_url?: string;
+ statuses_url?: string;
+ subscribers_count?: number;
+ subscribers_url?: string;
+ subscription_url?: string;
+ svn_url?: string;
+ tags_url?: string;
+ teams_url?: string;
+ temp_clone_token?: string;
+ topics?: string[];
+ trees_url?: string;
+ updated_at?: string;
+ url?: string;
+ use_squash_pr_title_as_default?: boolean;
+ visibility?: string;
+ watchers_count?: number;
+ [property: string]: any;
+}
+
+export interface Owner {
+ avatar_url?: string;
+ events_url?: string;
+ followers_url?: string;
+ following_url?: string;
+ gists_url?: string;
+ gravatar_id?: string;
+ html_url?: string;
+ id?: number;
+ login?: string;
+ node_id?: string;
+ organizations_url?: string;
+ received_events_url?: string;
+ repos_url?: string;
+ site_admin?: boolean;
+ starred_url?: string;
+ subscriptions_url?: string;
+ type?: string;
+ url?: string;
+ [property: string]: any;
+}
+
+export interface TemplateRepositoryPermissions {
+ admin?: boolean;
+ maintain?: boolean;
+ pull?: boolean;
+ push?: boolean;
+ triage?: boolean;
+ [property: string]: any;
+}
+
+export enum StateReason {
+ Completed = 'completed',
+ NotPlanned = 'not_planned',
+ Reopened = 'reopened',
+}
+
+/**
+ * A GitHub user.
+ */
+export interface TentacledSimpleUser {
+ avatar_url: string;
+ email?: null | string;
+ events_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ gravatar_id: null | string;
+ html_url: string;
+ id: number;
+ login: string;
+ name?: null | string;
+ node_id: string;
+ organizations_url: string;
+ received_events_url: string;
+ repos_url: string;
+ site_admin: boolean;
+ starred_at?: string;
+ starred_url: string;
+ subscriptions_url: string;
+ type: string;
+ url: string;
+ [property: string]: any;
+}
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
new file mode 100644
index 000000000..b95417aa1
--- /dev/null
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -0,0 +1,254 @@
+// Final file url reference: "https://browndash.com/files/images/upload_cb31bc0fda59c96ca14193ec494f80cf_o.jpg" />
+
+import { Octokit } from '@octokit/core';
+import { Networking } from '../../Network';
+import { Issue } from './reportManagerSchema';
+
+// enums and interfaces
+
+export enum ViewState {
+ VIEW,
+ CREATE,
+}
+
+export enum Priority {
+ HIGH = 'priority-high',
+ MEDIUM = 'priority-medium',
+ LOW = 'priority-low',
+}
+
+export enum BugType {
+ BUG = 'bug',
+ COSMETIC = 'cosmetic',
+ DOCUMENTATION = 'documentation',
+ ENHANCEMENT = 'enhancement',
+}
+
+export interface FileData {
+ _id: string;
+ file: File;
+}
+
+export interface ReportForm {
+ title: string;
+ description: string;
+ type: BugType;
+ priority: Priority;
+ mediaFiles: FileData[];
+}
+
+export type ReportFormKey = keyof ReportForm;
+
+export const emptyReportForm = {
+ title: '',
+ description: '',
+ type: BugType.BUG,
+ priority: Priority.MEDIUM,
+ mediaFiles: [],
+};
+
+// interfacing with Github
+
+/**
+ * Fetches issues from Github.
+ * @returns array of all issues
+ */
+export const getAllIssues = async (octokit: Octokit): Promise<any[]> => {
+ const res = await octokit.request('GET /repos/{owner}/{repo}/issues', {
+ owner: 'brown-dash',
+ repo: 'Dash-Web',
+ per_page: 80,
+ });
+
+ // 200 status means success
+ if (res.status === 200) {
+ return res.data;
+ } else {
+ throw new Error('Error getting issues');
+ }
+};
+
+/**
+ * Formats issue title.
+ *
+ * @param title title of issue
+ * @param userEmail email of issue submitter
+ * @returns formatted title
+ */
+export const formatTitle = (title: string, userEmail: string): string => `${title} - ${userEmail.replace('@brown.edu', '')}`;
+
+// uploading
+
+// turns an upload link -> server link
+// ex:
+// C: /Users/dash/Documents/GitHub/Dash-Web/src/server/public/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2.png
+// -> https://browndash.com/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2_l.png
+export const fileLinktoServerLink = (fileLink: string): string => {
+ const serverUrl = window.location.href.includes('browndash') ? 'https://browndash.com/' : 'http://localhost:1050/';
+
+ const regex = 'public';
+ const publicIndex = fileLink.indexOf(regex) + regex.length;
+
+ const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`;
+ return finalUrl;
+};
+
+/**
+ * Gets the server file path.
+ *
+ * @param link response from file upload
+ * @returns server file path
+ */
+export const getServerPath = (link: any): string => {
+ return link.result.accessPaths.agnostic.server as string;
+};
+
+/**
+ * Uploads media files to the server.
+ * @returns the server paths or undefined on error
+ */
+export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<string[] | undefined> => {
+ try {
+ // need to always upload to browndash
+ const links = await Networking.UploadFilesToServer(mediaFiles.map(file => ({ file: file.file })));
+ return (links ?? []).map(getServerPath).map(fileLinktoServerLink);
+ } catch (err) {
+ if (err instanceof Error) {
+ alert(err.message);
+ } else {
+ alert(err);
+ }
+ }
+};
+
+// helper functions
+
+/**
+ * Returns when the issue passes the current filters.
+ *
+ * @param issue issue to check
+ * @returns boolean indicating whether the issue passes the current filters
+ */
+export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bugFilter: string | null) => {
+ let passesPriority = true;
+ let passesBug = true;
+ if (priorityFilter) {
+ passesPriority = issue.labels.some(label => {
+ if (typeof label === 'string') {
+ return label === priorityFilter;
+ } else {
+ return label.name === priorityFilter;
+ }
+ });
+ }
+ if (bugFilter) {
+ passesBug = issue.labels.some(label => {
+ if (typeof label === 'string') {
+ return label === bugFilter;
+ } else {
+ return label.name === bugFilter;
+ }
+ });
+ }
+ return passesPriority && passesBug;
+};
+
+// sets and lists
+
+export const prioritySet = new Set(Object.values(Priority));
+export const bugSet = new Set(Object.values(BugType));
+
+export const priorityDropdownItems = [
+ {
+ text: 'Low',
+ val: Priority.LOW,
+ },
+ {
+ text: 'Medium',
+ val: Priority.MEDIUM,
+ },
+ {
+ text: 'High',
+ val: Priority.HIGH,
+ },
+];
+
+export const bugDropdownItems = [
+ {
+ text: 'Bug',
+ val: BugType.BUG,
+ },
+ {
+ text: 'Poor Design or Cosmetic',
+ val: BugType.COSMETIC,
+ },
+ {
+ text: 'Documentation',
+ val: BugType.DOCUMENTATION,
+ },
+ {
+ text: 'New feature or request',
+ val: BugType.ENHANCEMENT,
+ },
+];
+
+// colors
+
+// [bgColor, color]
+export const priorityColors: { [key: string]: string[] } = {
+ 'priority-low': ['#d4e0ff', '#000000'],
+ 'priority-medium': ['#6a91f6', '#ffffff'],
+ 'priority-high': ['#003cd5', '#ffffff'],
+};
+
+// [bgColor, color]
+export const bugColors: { [key: string]: string[] } = {
+ bug: ['#fe6d6d', '#ffffff'],
+ cosmetic: ['#c650f4', '#ffffff'],
+ documentation: ['#36acf0', '#ffffff'],
+ enhancement: ['#36d4f0', '#ffffff'],
+};
+
+export const getLabelColors = (label: string): string[] => {
+ if (prioritySet.has(label as Priority)) {
+ return priorityColors[label];
+ } else if (bugSet.has(label as BugType)) {
+ return bugColors[label];
+ }
+ return ['#0f73f6', '#ffffff'];
+};
+
+const hexToRgb = (hex: string) => {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result
+ ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16),
+ }
+ : {
+ r: 0,
+ g: 0,
+ b: 0,
+ };
+};
+
+// function that returns whether text should be light on the given bg color
+export const isDarkMode = (bgHex: string): boolean => {
+ const { r, g, b } = hexToRgb(bgHex);
+ return r * 0.299 + g * 0.587 + b * 0.114 <= 186;
+};
+
+export const lightColors = {
+ text: '#000000',
+ textGrey: '#5c5c5c',
+ border: '#b8b8b8',
+};
+
+export const darkColors = {
+ text: '#ffffff',
+ textGrey: '#d6d6d6',
+ border: '#717171',
+};
+
+export const dashBlue = '#4476f7';
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index 8a0e5480e..b205a0f1e 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -5,11 +5,15 @@
position: absolute;
z-index: 10001;
height: $antimodemenu-height;
- background: $dark-gray;
- border-bottom: $standard-border;
+ width: fit-content;
+ border-radius: $standard-border-radius;
+ overflow: hidden;
// box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
// border-radius: 0px 6px 6px 6px;
display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 3px;
&.with-rows {
flex-direction: column
@@ -20,30 +24,6 @@
height: 35px;
}
- .antimodeMenu-button {
- background-color: transparent;
- width: 35px;
- height: 35px;
- padding: 5;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
-
- .svg {
- margin: 0;
- }
-
- &.active {
- background-color: #121212;
- }
- }
-
- .antimodeMenu-button:hover {
- background-color: rgba(0, 0, 0, 0.4);
- }
-
.antimodeMenu-dragger {
height: 100%;
transition: width .2s;
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index de1207ce4..c41ea7053 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,6 +1,8 @@
import React = require('react');
import { observable, action, runInAction } from 'mobx';
import './AntimodeMenu.scss';
+import { StrCast } from '../../fields/Types';
+import { Doc } from '../../fields/Doc';
export interface AntimodeMenuProps {}
/**
@@ -148,6 +150,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
left: this._left,
top: this._top,
opacity: this._opacity,
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
@@ -173,6 +176,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
height: 'inherit',
width: 200,
opacity: this._opacity,
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
@@ -195,6 +199,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
left: this._left,
top: this._top,
opacity: this._opacity,
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
transitionProperty: this._transitionProperty,
transitionDuration: this._transitionDuration,
transitionDelay: this._transitionDelay,
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 14c922526..c779ce8c4 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -3,7 +3,7 @@ import axios from 'axios';
import { action, computed, IReactionDisposer, reaction } from 'mobx';
import { observer } from 'mobx-react';
import Waveform from 'react-audio-waveform';
-import { Doc } from '../../fields/Doc';
+import { Doc, NumListCast } from '../../fields/Doc';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast } from '../../fields/Types';
@@ -54,7 +54,7 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
}
@computed get audioBuckets() {
- return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec('number'), []);
+ return NumListCast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
}
audioBucketField = (start: number, end: number, zoomFactor: number) => this.props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
@@ -102,7 +102,7 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
render() {
return (
<div className="audioWaveform">
- <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} />
+ <Waveform color={Colors.MEDIUM_BLUE_ALT} height={this.waveHeight} barWidth={200 / this.audioBuckets.length} pos={this.props.duration} duration={this.props.duration} peaks={Array.from(this.audioBuckets)} progressColor={Colors.MEDIUM_BLUE_ALT} />
</div>
);
}
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index cbe14060a..588eff1d1 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -7,6 +7,7 @@
box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
flex-direction: column;
background: whitesmoke;
+ color: black;
border-radius: 3px;
}
@@ -51,13 +52,29 @@
user-select: none;
transition: all 0.1s;
border-style: none;
- // padding: 10px 0px 10px 0px;
+ position: relative;
white-space: nowrap;
font-size: 13px;
letter-spacing: 2px;
text-transform: uppercase;
padding-right: 30px;
+ .contextMenu-item-background {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 0;
+ filter: opacity(0);
+ }
+
+ &:hover {
+ .contextMenu-item-background {
+ filter: opacity(0.2) !important;
+ }
+ }
+
.contextMenu-item-icon-background {
pointer-events: all;
background-color: transparent;
@@ -132,11 +149,6 @@
// border-top: solid 1px; //TODO:glr clean
}
-.contextMenu-item:hover {
- transition: all 0.1s ease;
- background: $light-blue;
-}
-
.contextMenu-description {
margin-left: 5px;
text-align: left;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index e4c3e864b..8412a9aae 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -5,6 +5,8 @@ import { observer } from 'mobx-react';
import './ContextMenu.scss';
import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
import { Utils } from '../../Utils';
+import { StrCast } from '../../fields/Types';
+import { Doc } from '../../fields/Doc';
@observer
export class ContextMenu extends React.Component {
@@ -190,7 +192,11 @@ export class ContextMenu extends React.Component {
}
return this.filteredItems.map((value, index) =>
Array.isArray(value) ? (
- <div className="contextMenu-group">
+ <div
+ className="contextMenu-group"
+ style={{
+ background: StrCast(Doc.UserDoc().userVariantColor),
+ }}>
<div className="contextMenu-description">{value.join(' -> ')}</div>
</div>
) : (
@@ -213,13 +219,18 @@ export class ContextMenu extends React.Component {
this._height = Number(getComputedStyle(r).height.replace('px', ''));
}
})}
- style={{ left: this.pageX, ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }) }}>
+ style={{
+ left: this.pageX,
+ ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ }}>
{!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 />
+ <input style={{ color: 'black' }} className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
</span>
)}
{this.menuItems}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 33f250986..daa2c152a 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -4,6 +4,8 @@ import { observer } from 'mobx-react';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { UndoManager } from '../util/UndoManager';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
export interface OriginalMenuProps {
description: string;
@@ -90,6 +92,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
</span>
) : null}
<div className="contextMenu-description">{this.props.description.replace(':', '')}</div>
+ <div className={`contextMenu-item-background`}
+ style={{
+ background: StrCast(Doc.UserDoc().userColor)
+ }}
+ />
</div>
);
} else if ('subitems' in this.props) {
@@ -103,6 +110,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
style={{
marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%',
marginTop,
+ background: StrCast(Doc.UserDoc().userBackgroundColor)
}}>
{this._items.map(prop => (
<ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
@@ -133,6 +141,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
{this.props.description}
<FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
</div>
+ <div className={`contextMenu-item-background`}
+ style={{
+ background: StrCast(Doc.UserDoc().userColor)
+ }}
+ />
{submenu}
</div>
);
diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss
index b8a6f6c05..6be2133ef 100644
--- a/src/client/views/DashboardView.scss
+++ b/src/client/views/DashboardView.scss
@@ -1,12 +1,15 @@
-@import "./global/globalCssVariables";
-
+@import './global/globalCssVariables';
.dashboard-view {
- padding: 50px;
- display: flex;
- flex-direction: row;
- width: 100%;
- position: absolute;
+ padding: 50px;
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ position: absolute;
+ height: 100%;
+ width:100%;
+ padding-right: 0px;
+ overflow: auto;
.left-menu {
display: flex;
@@ -14,26 +17,28 @@
flex-direction: column;
width: 250px;
min-width: 250px;
+ gap: 5px;
}
- .all-dashboards {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- overflow-y: scroll;
- }
+ .all-dashboards {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ overflow-y: auto;
+ width: 100%;
+ }
}
.text-button {
cursor: pointer;
- padding: 3px 0;
- &:hover {
- font-weight: 500;
- }
+ padding: 3px 0;
+ &:hover {
+ font-weight: 500;
+ }
- &.selected {
- font-weight: 700;
- }
+ &.selected {
+ font-weight: 700;
+ }
}
.new-dashboard-button {
@@ -56,15 +61,26 @@
display: flex;
justify-content: center;
align-items: center;
+ position: relative;
&:hover {
color: $light-blue;
border: solid 2px $light-blue;
}
+
+ .background {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ z-index: -1;
+ }
}
.dashboard-container {
border-radius: 10px;
+ position: relative;
cursor: pointer;
width: 250px;
height: 200px;
@@ -74,35 +90,55 @@
margin: 0 0px 30px 30px;
overflow: hidden;
- &:hover{
+ &:hover {
outline: solid 2px $light-blue;
- }
+ }
- .title {
- margin: 10px;
- font-weight: 500;
- }
+ .title {
+ margin: 10px;
+ font-weight: 500;
+ }
- img {
- width: auto;
- height: 80%;
- }
+ img {
+ width: auto;
+ height: 80%;
+ }
- .info {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding: 0px 10px;
- }
+ .info {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0px 10px;
+ }
+ .dashboard-status,
+ .dashboard-status-shared {
+ font-size: 9;
+ left: 10%;
+ position: relative;
+ top: -5;
+ }
+ .dashboard-status-shared {
+ background: 'lightgreen';
+ }
.more {
z-index: 100;
}
+
+ .background {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ z-index: -1;
+ }
}
.new-dashboard {
color: $dark-gray;
+ padding: 10px;
display: flex;
width: 100%;
height: 100%;
@@ -136,4 +172,4 @@
flex-direction: row;
justify-content: flex-end;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 50cf2226e..3e4827c83 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -1,16 +1,17 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, FontSize, IconButton, Size } from 'browndash-components';
+import { Button, ColorPicker, EditableText, FontSize, IconButton, Size, Type } from 'browndash-components';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import { FaPlus } from 'react-icons/fa';
import { Doc, DocListCast, DocListCastAsync } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
+import { AclPrivate, DocAcl } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { listSpec } from '../../fields/Schema';
import { ScriptField } from '../../fields/ScriptField';
-import { Cast, DocCast, ImageCast, StrCast } from '../../fields/Types';
+import { Cast, ImageCast, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
import { CollectionViewType } from '../documents/DocumentTypes';
@@ -24,8 +25,7 @@ import { ContextMenu } from './ContextMenu';
import './DashboardView.scss';
import { Colors } from './global/globalEnums';
import { MainViewModal } from './MainViewModal';
-import { ButtonType } from './nodes/button/FontIconBox';
-import { FaPlus } from 'react-icons/fa';
+import { ButtonType } from './nodes/FontIconBox/FontIconBox';
enum DashboardGroup {
MyDashboards,
@@ -43,13 +43,16 @@ export class DashboardView extends React.Component {
@observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
@observable private newDashboardName: string | undefined = undefined;
- @observable private newDashboardColor: string | undefined = undefined;
+ @observable private newDashboardColor: string | undefined = '#AFAFAF';
@action abortCreateNewDashboard = () => {
this.newDashboardName = undefined;
};
@action setNewDashboardName(name: string) {
this.newDashboardName = name;
}
+ @action setNewDashboardColor(color: string) {
+ this.newDashboardColor = color;
+ }
@action
selectDashboardGroup = (group: DashboardGroup) => {
@@ -57,28 +60,30 @@ export class DashboardView extends React.Component {
};
clickDashboard = (e: React.MouseEvent, dashboard: Doc) => {
- Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
- Doc.ActiveDashboard = dashboard;
+ if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) {
+ DashboardView.openSharedDashboard(dashboard);
+ } else {
+ Doc.ActiveDashboard = dashboard;
+ }
Doc.ActivePage = 'dashboard';
};
- getDashboards = () => {
+ getDashboards = (whichGroup: DashboardGroup) => {
const allDashboards = DocListCast(Doc.MyDashboards.data);
- if (this.selectedDashboardGroup === DashboardGroup.MyDashboards) {
+ if (whichGroup === DashboardGroup.MyDashboards) {
return allDashboards.filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail);
- } else {
- const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking);
- return sharedDashboards;
}
+ const sharedDashboards = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
+ return sharedDashboards;
};
isUnviewedSharedDashboard = (dashboard: Doc): boolean => {
- // const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking);
+ // const sharedDashboards = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc._type_collection === CollectionViewType.Docking);
return !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard);
};
getSharedDashboards = () => {
- const sharedDashs = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking);
+ const sharedDashs = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc._type_collection === CollectionViewType.Docking);
return sharedDashs.filter(dashboard => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard));
};
@@ -95,23 +100,25 @@ export class DashboardView extends React.Component {
const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1;
const placeholder = `Dashboard ${dashboardCount}`;
return (
- <div className="new-dashboard">
+ <div
+ className="new-dashboard"
+ style={{
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
+ color: StrCast(Doc.UserDoc().userColor),
+ }}>
<div className="header">Create New Dashboard</div>
- <div className="title-input">
- Title
- <input className="input" placeholder={placeholder} onChange={e => this.setNewDashboardName((e.target as any).value)} />
- </div>
- <div className="color-picker">
- Background
- <ColorPicker
- onChange={color => {
- this.newDashboardColor = color;
- }}
- />
- </div>
+ <EditableText formLabel="Title" placeholder={placeholder} type={Type.SEC} color={StrCast(Doc.UserDoc().userColor)} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
+ <ColorPicker
+ formLabel="Background" //
+ colorPickerType="github"
+ type={Type.TERT}
+ selectedColor={this.newDashboardColor}
+ setFinalColor={this.setNewDashboardColor}
+ setSelectedColor={this.setNewDashboardColor}
+ />
<div className="button-bar">
- <Button text="Cancel" onClick={this.abortCreateNewDashboard} />
- <Button text="Create" onClick={() => this.createNewDashboard(this.newDashboardName!, this.newDashboardColor)} />
+ <Button text="Cancel" color={StrCast(Doc.UserDoc().userColor)} onClick={this.abortCreateNewDashboard} />
+ <Button type={Type.TERT} text="Create" color={StrCast(Doc.UserDoc().userVariantColor)} onClick={() => this.createNewDashboard(this.newDashboardName!, this.newDashboardColor)} />
</div>
</div>
);
@@ -150,32 +157,43 @@ export class DashboardView extends React.Component {
};
render() {
+ const color = StrCast(Doc.UserDoc().userColor);
+ const variant = StrCast(Doc.UserDoc().userVariantColor);
return (
<>
<div className="dashboard-view">
<div className="left-menu">
- <div className="new-dashboard-button">
- <Button icon={<FaPlus />} size={Size.MEDIUM} text="New" onClick={() => this.setNewDashboardName('')} />
- </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>
+ <Button text={'My Dashboards'} active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align={'flex-start'} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
+ <Button
+ text={'Shared Dashboards' + ' (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
+ active={this.selectedDashboardGroup === DashboardGroup.SharedDashboards}
+ color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'green' : color}
+ align={'flex-start'}
+ onClick={() => this.selectDashboardGroup(DashboardGroup.SharedDashboards)}
+ fillWidth
+ />
+ <Button icon={<FaPlus />} color={variant} iconPlacement="left" text="New Dashboard" type={Type.TERT} onClick={() => this.setNewDashboardName('')} />
</div>
<div className="all-dashboards">
- {this.getDashboards().map(dashboard => {
+ {this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
const href = ImageCast(dashboard.thumb)?.url.href;
+ const shared = Object.keys(dashboard[DocAcl])
+ .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key))
+ .some(key => dashboard[DocAcl][key] !== AclPrivate);
return (
- <div className="dashboard-container" key={dashboard[Id]} onContextMenu={e => this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}>
+ <div
+ className="dashboard-container"
+ key={dashboard[Id]}
+ style={{ background: this.isUnviewedSharedDashboard(dashboard) && this.selectedDashboardGroup === DashboardGroup.SharedDashboards ? '#6CB982' : shared ? variant : '' }}
+ 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='
}
/>
<div className="info">
- <input style={{ border: 'unset' }} className="input" onClick={e => e.stopPropagation()} defaultValue={StrCast(dashboard.title)} onChange={e => (Doc.GetProto(dashboard).title = (e.target as any).value)} />
+ <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (Doc.GetProto(dashboard).title = val)} />
{this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>}
<div
className="more"
@@ -188,9 +206,17 @@ export class DashboardView extends React.Component {
e.stopPropagation();
this.onContextMenu(dashboard, e);
}}>
- <Button size={Size.SMALL} icon={<FontAwesomeIcon color="black" size="lg" icon="bars" />} />
+ <Button size={Size.SMALL} color={color} icon={<FontAwesomeIcon color={color} icon="bars" />} />
</div>
</div>
+ <div
+ className={`background`}
+ style={{
+ background: StrCast(Doc.UserDoc().userColor),
+ filter: 'opacity(0.2)',
+ }}
+ />
+ <div className={'dashboard-status' + (shared ? '-shared' : '')}>{shared ? 'shared' : ''}</div>
</div>
);
})}
@@ -200,6 +226,13 @@ export class DashboardView extends React.Component {
this.setNewDashboardName('');
}}>
+
+ <div
+ className={`background`}
+ style={{
+ background: StrCast(Doc.UserDoc().userColor),
+ filter: 'opacity(0.2)',
+ }}
+ />
</div>
</div>
</div>
@@ -221,6 +254,11 @@ export class DashboardView extends React.Component {
return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
}
+ public static openSharedDashboard = (dashboard: Doc) => {
+ Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
+ DashboardView.openDashboard(Doc.BestEmbedding(dashboard));
+ };
+
/// 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) => {
@@ -347,13 +385,13 @@ export class DashboardView extends React.Component {
},
],
};
- Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true);
+ if (dashboard.dockingConfig && dashboard.dockingConfig !== Doc.GetProto(dashboard).dockingConfig) dashboard.dockingConfig = JSON.stringify(reset);
+ else Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true);
return reset;
};
public static createNewDashboard = (id?: string, name?: string, background?: string) => {
- const dashboards = Doc.MyDashboards;
- const dashboardCount = DocListCast(dashboards.data).length + 1;
+ const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1;
const freeformOptions: DocumentOptions = {
x: 0,
y: 400,
@@ -368,12 +406,9 @@ export class DashboardView extends React.Component {
const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
- // switching the tabs from the datadoc to the regular doc
- const dashboardTabs = DocListCast(dashboardDoc[DocData].data);
- dashboardDoc.data = new List<Doc>(dashboardTabs);
dashboardDoc['pane-count'] = 1;
- Doc.AddDocToList(dashboards, 'data', dashboardDoc);
+ Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
DashboardView.SetupDashboardTrails(dashboardDoc);
@@ -389,10 +424,11 @@ export class DashboardView extends React.Component {
_forceActive: true,
_width: 30,
_height: 30,
- _stayInCollection: true,
+ _dragOnlyWithinContainer: true,
_layout_hideContextMenu: true,
title: 'New trail',
toolTip: 'Create new trail',
+ color: Colors.BLACK,
btnType: ButtonType.ClickButton,
buttonText: 'New trail',
icon: 'plus',
@@ -408,12 +444,12 @@ export class DashboardView extends React.Component {
title: 'My Trails',
_layout_showTitle: 'title',
_height: 100,
- treeViewHideTitle: true,
+ treeView_HideTitle: true,
_layout_fitWidth: true,
_gridGap: 5,
_forceActive: true,
- childDropAction: 'embed',
- treeViewTruncateTitleWidth: 150,
+ childDragAction: 'embed',
+ treeView_TruncateTitleWidth: 150,
ignoreClick: true,
layout_headerButton: myTrailsBtn,
contextMenuIcons: new List<string>(['plus']),
@@ -421,11 +457,11 @@ export class DashboardView extends React.Component {
_lockedPosition: true,
layout_boxShadow: '0 0',
childDontRegisterViews: true,
- targetDropAction: 'same',
+ dropAction: 'same',
isSystem: true,
layout_explainer: 'All of the trails that you have created will appear here.',
};
- const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeViewChildDoubleClick: 'openPresentation(documentView.rootDoc)' });
+ const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeView_ChildDoubleClick: 'openPresentation(documentView.rootDoc)' });
dashboardDoc.myTrails = new PrefetchProxy(myTrails);
const contextMenuScripts = [reqdBtnScript.onClick];
@@ -435,10 +471,6 @@ export class DashboardView extends React.Component {
}
}
-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');
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index db24229dc..e076e69ca 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,15 +1,14 @@
import { action, computed, observable } from 'mobx';
import { DateField } from '../../fields/DateField';
-import { DocListCast, Opt, Doc } from '../../fields/Doc';
+import { Doc, DocListCast, HierarchyMapping, Opt, ReverseHierarchyMap } from '../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocAcl, DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
-import { Cast, ScriptCast } from '../../fields/Types';
-import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util';
+import { Cast, DocCast, StrCast } from '../../fields/Types';
+import { distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util';
import { returnFalse } from '../../Utils';
import { DocUtils } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { InteractionUtils } from '../util/InteractionUtils';
-import { UndoManager } from '../util/UndoManager';
import { DocumentView } from './nodes/DocumentView';
import { Touchable } from './Touchable';
@@ -146,13 +145,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
const toRemove = value.filter(v => docs.includes(v));
if (toRemove.length !== 0) {
- const recent = Doc.MyRecentlyClosed;
+ const recent = this.rootDoc !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
toRemove.forEach(doc => {
leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+ Doc.RemoveDocFromList(Doc.GetProto(doc), 'proto_embeddings', doc);
doc.embedContainer = undefined;
if (recent) {
- Doc.RemoveDocFromList(recent, 'data', doc);
doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
}
});
@@ -172,8 +171,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
return true;
}
const first = doc instanceof Doc ? doc : doc[0];
- if (!first?._stayInCollection && addDocument !== returnFalse) {
- return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey));
+ if (!first?._dragOnlyWithinContainer && addDocument !== returnFalse) {
+ return this.removeDocument(doc, annotationKey, false) && addDocument(doc, annotationKey);
}
return false;
};
@@ -183,7 +182,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
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[DocData];
+ const targetDataDoc = this.rootDoc[DocData];
const effectiveAcl = GetEffectiveAcl(targetDataDoc);
if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
@@ -191,36 +190,16 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
}
const added = docs;
if (added.length) {
- const aclKeys = Object.keys(this.props.Document[DocAcl] ?? {});
- aclKeys.forEach(key =>
- added.forEach(d => {
- if (d.author === denormalizeEmail(key.substring(4)) && !d.createdFrom) {
- distributeAcls(key, SharingPermissions.Admin, d);
- }
- })
- );
-
- if (effectiveAcl === AclAugment) {
- added.map(doc => {
- if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
- doc.embedContainer = this.props.Document;
- if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
- Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+ if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
+ added.forEach(doc => {
+ doc._dragOnlyWithinContainer = undefined;
+ if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
+ Doc.SetContainer(doc, this.rootDoc);
+ inheritParentAcls(targetDataDoc, doc, true);
});
- } 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.embedContainer = this.props.Document;
- if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
-
- Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
- });
+
const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
- if (annoDocs instanceof List) annoDocs.push(...added);
+ if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField(new Date(Date.now()));
}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 35b0b22a8..345135a1a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -464,6 +464,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
action(() => {
this._isRecording = false;
this._stopFunc();
+ b.end();
}),
emptyFunction
);
@@ -562,10 +563,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const rootDoc = rootView?.rootDoc;
if (rootDoc) {
const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc;
- const trail = DocCast(anchor.presTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
- if (trail !== anchor.presTrail) {
+ const trail = DocCast(anchor.presentationTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
+ if (trail !== anchor.presentationTrail) {
DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
- anchor.presTrail = trail;
+ anchor.presentationTrail = trail;
}
Doc.ActivePresentation = trail;
this.props.views().lastElement()?.props.addDocTab(trail, OpenWhere.replaceRight);
@@ -588,7 +589,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div style={{ position: 'absolute', zIndex: 1000 }}>
<LinkPopup
key="popup"
- showPopup={this._showLinkPopup}
linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))}
linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
linkFrom={() => this.props.views().lastElement()?.rootDoc}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index ccac5ffe4..ca3610cc0 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -112,6 +112,33 @@ $resizeHandler: 8px;
}
}
+ .documentDecorations-lockButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: grey;
+ border: solid 1.5px rgb(72, 71, 71);
+ color: grey;
+ transition: 0.1s ease;
+ opacity: 1;
+ pointer-events: all;
+ width: 20px;
+ height: 20px;
+ min-width: 20px;
+ border-radius: 100%;
+ opacity: 0.5;
+ cursor: pointer;
+
+ &:hover {
+ color: rgb(72, 71, 71);
+ opacity: 1;
+ }
+
+ > svg {
+ margin: 0;
+ }
+ }
+
.documentDecorations-minimizeButton {
display: flex;
align-items: center;
@@ -152,6 +179,7 @@ $resizeHandler: 8px;
display: flex;
height: 20px;
border-radius: 8px;
+ gap: 2px;
outline: none;
border: none;
opacity: 0.3;
@@ -186,6 +214,79 @@ $resizeHandler: 8px;
}
}
+ .documentDecorations-share {
+ background: none;
+ opacity: 1;
+ grid-column: 3;
+ pointer-events: auto;
+ min-width: fit-content;
+ text-align: center;
+ display: flex;
+ height: 21px;
+ opacity: 0.3;
+ &:hover {
+ opacity: 1;
+ }
+
+
+ .checkbox{
+ display: inline;
+
+ .checkbox-box{
+ display: inline;
+ position: relative;
+ top: -2.5;
+ left: 35;
+ zoom: .7;
+ }
+
+ & .checkbox-text{
+ display: inline;
+ position: relative;
+ top: 1.5;
+ font-size: 8px;
+ }
+ }
+
+ .documentDecorations-shareNone{
+ width: calc(100% + 10px);
+ background: grey;
+ color: rgb(71, 71, 71);
+ border-radius: 8px;
+ border: 2px solid rgb(71, 71, 71);
+ }
+ .documentDecorations-shareEdit,
+ .documentDecorations-shareAdmin{
+ width: calc(100% + 10px);
+ background: rgb(254, 254, 199);
+ color: rgb(75, 75, 5);
+ border-radius: 8px;
+ border: 2px solid rgb(75, 75, 5);
+ }
+ .documentDecorations-shareAugment{
+ width: calc(100% + 10px);
+ background: rgb(208, 255, 208);
+ color:rgb(19, 80, 19);
+ border-radius: 8px;
+ border: 2px solid rgb(19, 80, 19);
+
+ }
+ .documentDecorations-shareView{
+ width: calc(100% + 10px);
+ background: rgb(213, 213, 255);
+ color: rgb(25, 25, 101);
+ border-radius: 8px;
+ border: 2px solid rgb(25, 25, 101);
+ }
+ .documentDecorations-shareNot-Shared{
+ width: calc(100% + 10px);
+ background: rgb(255, 207, 207);
+ color: rgb(146, 58, 58);
+ border-radius: 8px;
+ border: 2px solid rgb(146, 58, 58);
+ }
+ }
+
.documentDecorations-centerCont {
grid-column: 2;
background: none;
@@ -264,7 +365,7 @@ $resizeHandler: 8px;
.documentDecorations-lock {
position: relative;
background: black;
- color: gray;
+ color: rgb(145, 144, 144);
height: 14;
width: 14;
pointer-events: all;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 528b82e3e..f3daf3ffa 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -6,8 +6,8 @@ import { action, computed, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { FaUndo } from 'react-icons/fa';
import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field } from '../../fields/Doc';
-import { AclAdmin, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
+import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
import { InkField } from '../../fields/InkField';
import { RichTextField } from '../../fields/RichTextField';
import { ScriptField } from '../../fields/ScriptField';
@@ -64,6 +64,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@observable private _isRotating: boolean = false;
@observable private _isRounding: boolean = false;
@observable private _isResizing: boolean = false;
+ @observable private showLayoutAcl: boolean = false;
constructor(props: any) {
super(props);
@@ -162,33 +163,46 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
@action onContainerDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(
- this,
- e,
- e => this.onBackgroundMove(true, e),
- e => {},
- emptyFunction
- );
+ const first = SelectionManager.Views()[0];
+ const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc);
+ if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+ setupMoveUpEvents(
+ this,
+ e,
+ e => this.onBackgroundMove(true, e),
+ e => {},
+ emptyFunction
+ );
+ }
};
@action onTitleDown = (e: React.PointerEvent): void => {
- 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);
- })
- );
+ const first = SelectionManager.Views()[0];
+ const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc);
+ if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+ 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 first = SelectionManager.Views()[0];
+ const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc);
+ if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
+ return false;
+ }
const dragDocView = SelectionManager.Views()[0];
const containers = new Set<Doc | undefined>();
SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer)));
@@ -481,6 +495,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
const first = SelectionManager.Views()[0];
+ const effectiveAcl = GetEffectiveAcl(first.rootDoc);
+ if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false;
if (!first) return false;
let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
var fixedAspect = Doc.NativeAspect(first.layoutDoc);
@@ -601,9 +617,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}
let actualdW = Math.max(docwidth + dW * scale, 20);
let actualdH = Math.max(docheight + dH * scale, 20);
- let dX = !dWin ? 0 : scale * refCent[0] * (1 - (1 + dWin / refWidth));
- let dY = !dHin ? 0 : scale * refCent[1] * (1 - (1 + dHin / refHeight));
- const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false;
+ let dX = !dWin ? 0 : (scale * refCent[0] * -dWin) / refWidth;
+ let dY = !dHin ? 0 : (scale * refCent[1] * -dHin) / refHeight;
+ const preserveNativeDim = !doc._nativeHeightUnfrozen && !doc._nativeDimModifiable;
const fixedAspect = nwidth && nheight && (!doc._layout_fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
if (fixedAspect) {
if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
@@ -614,6 +630,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
} else {
if (!doc._layout_fitWidth || preserveNativeDim) {
actualdH = (nheight / nwidth) * actualdW;
+ dYin && (dY = -dW * scale * (nheight / nwidth));
doc._height = actualdH;
} else if (!modifyNativeDim || dragBotRight) {
doc._height = actualdH;
@@ -630,13 +647,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
} else {
if (!doc._layout_fitWidth || preserveNativeDim) {
actualdW = (nwidth / nheight) * actualdH;
+ dXin && (dX = -dH * scale * (nwidth / nheight));
doc._width = actualdW;
} else if (!modifyNativeDim || dragBotRight) {
doc._width = actualdW;
}
}
if (!modifyNativeDim) {
- actualdH = (nheight / nwidth) * docwidth; //, actualdH);
+ actualdH = (nheight / nwidth) * NumCast(doc._width); //, actualdH);
}
doc._height = actualdH;
}
@@ -746,9 +764,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
setTimeout(action(() => (this._showNothing = true)));
return null;
}
+
+ // sharing
+ const acl = GetEffectiveAcl(!this.showLayoutAcl ? Doc.GetProto(seldocview.rootDoc) : seldocview.rootDoc);
+ const docShareMode = HierarchyMapping.get(acl)!.name;
+ const shareMode = StrCast(docShareMode);
+ var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
+
// hide the decorations if the parent chooses to hide it or if the document itself hides it
const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
- const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
+ const hideResizers =
+ ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating;
const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
@@ -756,7 +782,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
hideDecorations ||
seldocview.props.hideOpenButton ||
seldocview.rootDoc.layout_hideOpenButton ||
- SelectionManager.Views().some(docView => docView.rootDoc._stayInCollection || docView.rootDoc.isGroup || docView.rootDoc.layout_hideOpenButton) ||
+ SelectionManager.Views().some(docView => docView.rootDoc._dragOnlyWithinContainer || docView.rootDoc.isGroup || docView.rootDoc.layout_hideOpenButton) ||
this._isRounding ||
this._isRotating;
const hideDeleteButton =
@@ -767,9 +793,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
seldocview.rootDoc.hideDeleteButton ||
SelectionManager.Views().some(docView => {
const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
- return (docView.rootDoc.stayInCollection && !docView.rootDoc._isTimelineLabel) || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
+ return 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, e => click!(e)))}>
@@ -802,6 +827,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const radiusHandle = (borderRadius / docMax) * maxDist;
const radiusHandleLocation = Math.min(radiusHandle, maxDist);
+ const sharingMenu = docShareMode ? (
+ <div className="documentDecorations-share">
+ <div className={`documentDecorations-share${shareMode}`}>
+ &nbsp;
+ {shareSymbolIcon + ' ' + shareMode}
+ &nbsp;
+ {/* {!Doc.noviceMode ? (
+ <div className="checkbox">
+ <div className="checkbox-box">
+ <input type="checkbox" checked={this.showLayoutAcl} onChange={action(() => (this.showLayoutAcl = !this.showLayoutAcl))} />
+ </div>
+ <div className="checkbox-text"> Layout </div>
+ </div>
+ ) : null}
+ &nbsp; */}
+ </div>
+ </div>
+ ) : (
+ <div />
+ );
+
const titleArea = this._editingTitle ? (
<input
ref={this._keyinput}
@@ -816,8 +862,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
onPointerDown={e => e.stopPropagation()}
/>
) : (
- <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}>
- <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${hideTitle ? '' : this.selectionTitle}`}</span>
+ <div
+ className="documentDecorations-title"
+ key="title"
+ onPointerDown={e => {
+ e.stopPropagation;
+ }}>
+ {hideTitle ? null : (
+ <span className={`documentDecorations-titleSpan${colorScheme}`} onPointerDown={this.onTitleDown}>
+ {this.selectionTitle}
+ </span>
+ )}
+ {sharingMenu}
{!useLock ? null : (
<Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
<div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown} onContextMenu={e => e.preventDefault()}>
@@ -827,6 +883,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
)}
</div>
);
+
return (
<div className={`documentDecorations${colorScheme}`} style={{ opacity: this._showNothing ? 0.1 : undefined }}>
<div
@@ -856,11 +913,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px',
}}>
- <div className="documentDecorations-topbar" style={{ display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined }} onPointerDown={this.onContainerDown}>
+ <div
+ className="documentDecorations-topbar"
+ style={{
+ color: 'black',
+ display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined,
+ }}
+ onPointerDown={this.onContainerDown}>
{hideDeleteButton ? null : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
{hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
- {hideTitle ? null : titleArea}
- {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as new embedding, shift: in new collection)')}
+ {titleArea}
+ {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
</div>
{hideResizers ? null : (
<>
@@ -916,7 +979,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}}>
{this._isRotating ? null : (
<Tooltip enterDelay={750} title={<div className="dash-tooltip">tap to set rotate center, drag to rotate</div>}>
- <div className="documentDecorations-rotation" style={{ pointerEvents: 'all' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
+ <div className="documentDecorations-rotation" style={{ pointerEvents: 'all', color: 'blue' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
<IconButton icon={<FaUndo />} color={Colors.LIGHT_GRAY} />
</div>
</Tooltip>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 7043edcee..d60617020 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -6,6 +6,7 @@ import { ObjectField } from '../../fields/ObjectField';
import './EditableView.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
+import { EditableText } from 'browndash-components';
export interface EditableProps {
/**
@@ -231,7 +232,7 @@ export class EditableView extends React.Component<EditableProps> {
onChange: this.props.autosuggestProps.onChange,
}}
/>
- ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? (
+ ) : (
<input
className="editableView-input"
ref={r => (this._inputref = r)}
@@ -247,23 +248,24 @@ export class EditableView extends React.Component<EditableProps> {
onClick={this.stopPropagation}
onPointerUp={this.stopPropagation}
/>
- ) : (
- <textarea
- className="editableView-input"
- ref={r => (this._inputref = r)}
- style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, 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}
- onChange={this.onChange}
- onKeyDown={this.onKeyDown}
- onKeyPress={this.stopPropagation}
- onPointerDown={this.stopPropagation}
- onClick={this.stopPropagation}
- onPointerUp={this.stopPropagation}
- />
);
+ // ) : (
+ // <textarea
+ // className="editableView-input"
+ // ref={r => (this._inputref = r)}
+ // style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, 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}
+ // onChange={this.onChange}
+ // onKeyDown={this.onKeyDown}
+ // onKeyPress={this.stopPropagation}
+ // onPointerDown={this.stopPropagation}
+ // onClick={this.stopPropagation}
+ // onPointerUp={this.stopPropagation}
+ // />
+ // );
}
render() {
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index c903f29ee..4f0460659 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -156,7 +156,7 @@
right: 0;
top: 0;
z-index: 1;
- background-color: #9f9f9f;
+ // background-color: #9f9f9f;
.filterBox-tree {
z-index: 0;
@@ -182,6 +182,7 @@
}
.filterBox-tree {
+ padding-top: 8px;
display: inline-block;
width: 100%;
margin-bottom: 10px;
@@ -189,3 +190,23 @@
overflow: auto;
}
}
+
+
+
+.filterBox-facetHeader{
+ display: flex;
+ align-items: center;
+ // float:right;
+
+ .filterBox-facetHeader-collapse{
+ float: right;
+ justify-items: right;
+ align-items: flex-end;
+ margin-left: auto;
+ margin-right: 9px;
+ }
+
+}
+
+
+
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index fe42628cd..63bd01b19 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -11,6 +11,8 @@ import './FilterPanel.scss';
import { FieldView } from './nodes/FieldView';
import { SearchBox } from './search/SearchBox';
import { undoable } from '../util/UndoManager';
+import { AiOutlineMinusSquare } from 'react-icons/ai';
+import { CiCircleRemove } from 'react-icons/ci';
interface filterProps {
rootDoc: Doc;
@@ -71,7 +73,7 @@ export class FilterPanel extends React.Component<filterProps> {
* @returns a string array of the current attributes
*/
@computed get currentFacets() {
- return this.activeFilters.map(filter => filter.split(':')[0]);
+ return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]);
}
gatherFieldValues(childDocs: Doc[], facetKey: string) {
@@ -108,8 +110,8 @@ export class FilterPanel extends React.Component<filterProps> {
@observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
@computed get activeFacets() {
const facets = new Map<string, 'text' | 'checkbox' | 'slider' | 'range'>(this._chosenFacets);
- StrListCast(this.targetDoc?._childFilters).map(filter => facets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'));
- setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'))));
+ StrListCast(this.targetDoc?._childFilters).map(filter => facets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox'));
+ setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox'))));
return facets;
}
/**
@@ -173,7 +175,8 @@ export class FilterPanel extends React.Component<filterProps> {
<div style={{ width: '100%' }}>
<Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
</div>
- <div className="filterBox-select-bool">
+ {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
+ {/* <div className="filterBox-select-bool">
<select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}>
{['AND', 'OR'].map(bool => (
<option value={bool} key={bool}>
@@ -181,13 +184,22 @@ export class FilterPanel extends React.Component<filterProps> {
</option>
))}
</select>
- </div>{' '}
+ </div>{' '} */}
</div>
<div className="filterBox-tree" key="tree">
{Array.from(this.activeFacets.keys()).map(facetHeader => (
<div>
- {facetHeader}
+ <div className="filterBox-facetHeader">
+ <div className="filterBox-facetHeader-Header"> </div>
+ {facetHeader.charAt(0).toUpperCase() + facetHeader.slice(1)}
+
+ <div className="filterBox-facetHeader-collapse">
+ <AiOutlineMinusSquare />
+ {/* <CiCircleRemove/> */}
+ </div>
+ </div>
+
{this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader)}
</div>
))}
@@ -203,8 +215,8 @@ export class FilterPanel extends React.Component<filterProps> {
<input
placeholder={
StrListCast(this.targetDoc._childFilters)
- .find(filter => filter.split(':')[0] === facetHeader)
- ?.split(':')[1] ?? '-empty-'
+ .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
+ ?.split(Doc.FilterSep)[1] ?? '-empty-'
}
onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)}
@@ -219,8 +231,8 @@ export class FilterPanel extends React.Component<filterProps> {
style={{ width: 20, marginLeft: 20 }}
checked={
StrListCast(this.targetDoc._childFilters)
- .find(filter => filter.split(':')[0] === facetHeader && filter.split(':')[1] == facetValue)
- ?.split(':')[2] === 'check'
+ .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue)
+ ?.split(Doc.FilterSep)[2] === 'check'
}
type={type}
onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0951bff22..35d6d73e4 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,20 +2,19 @@ import React = require('react');
import * as fitCurve from 'fit-curve';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
import { Doc, Opt } from '../../fields/Doc';
import { InkData, InkTool } from '../../fields/InkField';
-import { ScriptField } from '../../fields/ScriptField';
-import { Cast, FieldValue, NumCast } from '../../fields/Types';
+import { 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 { Transform } from '../util/Transform';
import './GestureOverlay.scss';
+import { InkTranscription } from './InkTranscription';
import {
ActiveArrowEnd,
ActiveArrowScale,
@@ -31,13 +30,11 @@ import {
SetActiveInkColor,
SetActiveInkWidth,
} from './InkingStroke';
-import { InkTranscription } from './InkTranscription';
-import { checkInksToGroup } from './nodes/button/FontIconBox';
+import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu';
+import { Touchable } from './Touchable';
+import { checkInksToGroup } from './global/globalScripts';
import { DocumentView } from './nodes/DocumentView';
import { RadialMenu } from './nodes/RadialMenu';
-import HorizontalPalette from './Palette';
-import { Touchable } from './Touchable';
-import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu';
interface GestureOverlayProps {
isActive: boolean;
@@ -79,7 +76,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
private _overlayRef = React.createRef<HTMLDivElement>();
private _d1: Doc | undefined;
private _inkToTextDoc: Doc | undefined;
- private _thumbDoc: Doc | undefined;
private thumbIdentifier?: number;
private pointerIdentifier?: number;
private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
@@ -93,78 +89,12 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
GestureOverlay.Instances.push(this);
}
- static setupThumbButtons(doc: Doc) {
- const docProtoData: { title: string; icon: string; drag?: string; toolType?: 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, isSystem: true }),
- backgroundColor: 'orange',
- },
- { title: 'interpret text', icon: 'font', toolType: 'inktotext', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'orange' },
- { title: 'ignore gestures', icon: 'signature', toolType: 'ignoregesture', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'green' },
- ];
- return docProtoData.map(data =>
- Docs.Create.FontIconDocument({
- _nativeWidth: 10,
- _nativeHeight: 10,
- _width: 10,
- _height: 10,
- title: data.title,
- icon: data.icon,
- toolType: data.toolType,
- _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,
- dragFactory: data.dragFactory,
- isSystem: 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',
- _layout_autoHeight: true,
- _yMargin: 5,
- linearView_IsExpanded: true,
- backgroundColor: 'white',
- isSystem: true,
- });
- thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], {
- _width: 300,
- _height: 25,
- _layout_autoHeight: true,
- linearView_IsExpanded: true,
- flexDirection: 'column',
- isSystem: true,
- });
- userDoc.thumbDoc = thumbDoc;
- }
- return Cast(userDoc.thumbDoc, Doc);
- }
-
componentWillUnmount() {
GestureOverlay.Instances.splice(GestureOverlay.Instances.indexOf(this), 1);
GestureOverlay.Instance = GestureOverlay.Instances.lastElement();
}
componentDidMount = () => {
GestureOverlay.Instance = this;
- 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
@@ -395,24 +325,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
this.thumbIdentifier = thumb?.identifier;
this._hands.set(thumb.identifier, fingers);
- const others = fingers.filter(f => f !== thumb);
- const minX = Math.min(...others.map(f => f.clientX));
- const minY = Math.min(...others.map(f => f.clientY));
-
- // load up the palette collection around the thumb
- const thumbDoc = await Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc);
- if (thumbDoc) {
- runInAction(() => {
- RadialMenu.Instance._display = false;
- this._inkToTextDoc = FieldValue(Cast(thumbDoc.inkToTextDoc, Doc));
- this._thumbDoc = thumbDoc;
- this._thumbX = thumb.clientX;
- this._thumbY = thumb.clientY;
- this._menuX = thumb.clientX + 50;
- this._menuY = thumb.clientY;
- this._palette = <HorizontalPalette key="palette" x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
- });
- }
this.removeMoveListeners();
document.removeEventListener('touchmove', this.handleHandMove);
@@ -462,11 +374,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
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) {
- this._thumbDoc.selectedIndex = Math.max(-1, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX));
- this._thumbX = pt.clientX;
- }
}
}
}
@@ -485,7 +392,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
if (this.thumbIdentifier) this._hands.delete(this.thumbIdentifier);
this._palette = undefined;
this.thumbIdentifier = undefined;
- this._thumbDoc = undefined;
// this chunk of code is for handling the ink to text toolglass
let scriptWorked = false;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 47dcdd2e4..7b693c8da 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -261,6 +261,18 @@ export class KeyManager {
case 't':
PromiseValue(Cast(Doc.UserDoc()['tabs-button-tools'], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
break;
+ case 'i':
+ const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
+ if (importBtn) {
+ MainView.Instance.selectMenu(importBtn);
+ }
+ break;
+ case 's':
+ const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
+ if (trailsBtn) {
+ MainView.Instance.selectMenu(trailsBtn);
+ }
+ break;
case 'f':
if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) {
SelectionManager.Views()[0].ComponentView?.search?.('', false, false);
@@ -272,26 +284,26 @@ export class KeyManager {
}
break;
case 'e':
- Doc.ActiveTool = InkTool.Eraser;
+ Doc.ActiveTool = (Doc.ActiveTool === InkTool.Eraser ? InkTool.None : InkTool.Eraser);
break;
case 'p':
- Doc.ActiveTool = InkTool.Pen;
- break;
- case 'o':
- const target = SelectionManager.Docs().lastElement();
- target && CollectionDockingView.OpenFullScreen(target);
+ Doc.ActiveTool = (Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen);
break;
case 'r':
preventDefault = false;
break;
case 'y':
- SelectionManager.DeselectAll();
- UndoManager.Redo();
+ if (Doc.ActivePage !== 'home') {
+ SelectionManager.DeselectAll();
+ UndoManager.Redo();
+ }
stopPropagation = false;
break;
case 'z':
- SelectionManager.DeselectAll();
- UndoManager.Undo();
+ if (Doc.ActivePage !== 'home') {
+ SelectionManager.DeselectAll();
+ UndoManager.Undo();
+ }
stopPropagation = false;
break;
case 'a':
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 9447b2e72..07e3270b1 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -182,8 +182,8 @@ export interface InkEndProps {
inkDoc: Doc;
inkView: InkingStroke;
screenSpaceLineWidth: number;
- startPt: PointData;
- endPt: PointData;
+ startPt: () => PointData;
+ endPt: () => PointData;
}
@observer
export class InkEndPtHandles extends React.Component<InkEndProps> {
@@ -191,29 +191,31 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
@observable _overEnd: boolean = false;
@action
- dragRotate = (e: React.PointerEvent, p1: () => { X: number; Y: number }, p2: () => { X: number; Y: number }) => {
+ dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
setupMoveUpEvents(
this,
e,
action(e => {
if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
// compute stretch factor by finding scaling along axis between start and end points
- const v1 = { X: p1().X - p2().X, Y: p1().Y - p2().Y };
- const v2 = { X: e.clientX - p2().X, Y: e.clientY - p2().Y };
+ const p1 = pt1();
+ const p2 = pt2();
+ const v1 = { X: p1.X - p2.X, Y: p1.Y - p2.Y };
+ const v2 = { X: e.clientX - p2.X, Y: e.clientY - p2.Y };
const v1len = Math.sqrt(v1.X * v1.X + v1.Y * v1.Y);
const v2len = Math.sqrt(v2.X * v2.X + v2.Y * v2.Y);
const scaling = v2len / v1len;
const v1n = { X: v1.X / v1len, Y: v1.Y / v1len };
const v2n = { X: v2.X / v2len, Y: v2.Y / v2len };
const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y);
- InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2(), v1n, e.shiftKey);
- InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, p2());
+ InkStrokeProperties.Instance.stretchInk(SelectionManager.Views(), scaling, p2, v1n, e.shiftKey);
+ InkStrokeProperties.Instance.rotateInk(SelectionManager.Views(), angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
return false;
}),
action(() => {
this.props.inkView.controlUndo?.end();
this.props.inkView.controlUndo = undefined;
- UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
+ UndoManager.FilterBatches(['stroke', 'x', 'y', 'width', 'height']);
}),
returnFalse
);
@@ -237,20 +239,8 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
);
return (
<svg>
- {hdl('start', this.props.startPt, (e: React.PointerEvent) =>
- this.dragRotate(
- e,
- () => this.props.startPt,
- () => this.props.endPt
- )
- )}
- {hdl('end', this.props.endPt, (e: React.PointerEvent) =>
- this.dragRotate(
- e,
- () => this.props.endPt,
- () => this.props.startPt
- )
- )}
+ {hdl('start', this.props.startPt(), (e: React.PointerEvent) => this.dragRotate(e, this.props.startPt, this.props.endPt))}
+ {hdl('end', this.props.endPt(), (e: React.PointerEvent) => this.dragRotate(e, this.props.endPt, this.props.startPt))}
</svg>
);
}
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
index f504890a5..c672824bf 100644
--- a/src/client/views/InkStroke.scss
+++ b/src/client/views/InkStroke.scss
@@ -17,6 +17,7 @@
display: flex;
align-items: center;
height: 100%;
+ width: 100%;
transition: inherit;
.inkStroke {
mix-blend-mode: multiply;
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index d28981e17..abc4381a6 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -202,7 +202,6 @@ export class InkStrokeProperties {
@action
rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: PointData) => {
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
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index e38474ea0..6c213f40f 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -105,7 +105,7 @@ export class InkTranscription extends React.Component {
: null;
}
- r.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+ r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
return (this._textRef = r);
};
@@ -124,9 +124,9 @@ export class InkTranscription extends React.Component {
const strokes: InkData[] = [];
const times: number[] = [];
validInks
- .filter(i => Cast(i.data, InkField))
+ .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField))
.forEach(i => {
- const d = Cast(i.data, InkField, null);
+ const d = Cast(i[Doc.LayoutFieldKey(i)], 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.author_date).getDate().getTime());
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index c915ae65a..b3647249a 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -21,7 +21,7 @@
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 { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
import { Height, Width } from '../../fields/DocSymbols';
@@ -88,11 +88,11 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
if (!addAsAnnotation && !pinProps) return this.rootDoc;
- const anchor = Docs.Create.InkConfigDocument({
+ const anchor = Docs.Create.ConfigDocument({
title: 'Ink anchor:' + this.rootDoc.title,
// set presentation timing for restoring shape
- presDuration: 1100,
- presTransition: 1000,
+ presentation_duration: 1100,
+ presentation_transition: 1000,
annotationOn: this.rootDoc,
});
if (anchor) {
@@ -297,6 +297,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
*/
nearestScreenPt = () => this._nearestScrPt;
+ @computed get screenCtrlPts() {
+ const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+ return 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] }));
+ }
+ startPt = () => this.screenCtrlPts[0];
+ endPt = () => this.screenCtrlPts.lastElement();
/**
* @param boundsLeft the screen space left coordinate of the ink stroke
* @param boundsTop the screen space top coordinate of the ink stroke
@@ -304,18 +316,10 @@ 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 { inkData, inkStrokeWidth } = this.inkScaledData();
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.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.stroke_startMarker);
const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
@@ -323,13 +327,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
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} inkDoc={inkDoc} startPt={screenPts[0]} endPt={screenPts.lastElement()} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
+ <InkEndPtHandles inkView={this} inkDoc={inkDoc} startPt={this.startPt} endPt={this.endPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
</div>
)
) : (
<div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
{InteractionUtils.CreatePolyline(
- screenPts,
+ this.screenCtrlPts,
0,
0,
Colors.MEDIUM_BLUE,
@@ -350,14 +354,23 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
1.0,
false
)}
- <InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={screenHdlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
- <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={screenHdlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
+ <InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={this.screenCtrlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
+ <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
</div>
);
};
_subContentView: DocComponentView | undefined;
setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
+ @computed get fillColor() {
+ const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
+ return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FillColor) ?? 'transparent';
+ }
+ @computed get strokeColor() {
+ const { inkData } = this.inkScaledData();
+ const fillColor = this.fillColor;
+ return !InkingStroke.IsClosed(inkData) && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
+ }
render() {
TraceMobx();
const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
@@ -367,8 +380,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
const markerScale = NumCast(this.layoutDoc.stroke_markerScale, 1);
const closed = InkingStroke.IsClosed(inkData);
const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
- const fillColor = isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FillColor) ?? 'transparent';
- const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
+ const fillColor = this.fillColor;
// bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
if (isInkMask && (this.layoutDoc[Width]() !== Math.round(this.layoutDoc[Width]()) || this.layoutDoc[Height]() !== Math.round(this.layoutDoc[Height]()))) {
@@ -383,7 +395,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
inkData,
inkLeft,
inkTop,
- strokeColor,
+ this.strokeColor,
inkStrokeWidth,
inkStrokeWidth,
StrCast(this.layoutDoc.stroke_lineJoin),
@@ -403,20 +415,23 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
);
const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
const highlightIndex = highlight?.highlightIndex;
- const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
+ const highlightColor =
+ (!this.props.isSelected() || !isInkMask) && highlight?.highlightIndex
+ ? highlight?.highlightColor
+ : StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
// Invisible polygonal line that enables the ink to be selected by the user.
- const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false, mask: boolean = false) =>
+ const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false) =>
InteractionUtils.CreatePolyline(
inkData,
inkLeft,
inkTop,
- highlightColor,
+ mask && highlightColor === 'transparent' ? this.strokeColor : highlightColor,
inkStrokeWidth,
inkStrokeWidth + (fillColor ? (closed ? 2 : (highlightIndex ?? 0) + 2) : 2),
StrCast(this.layoutDoc.stroke_lineJoin),
StrCast(this.layoutDoc.stroke_lineCap),
StrCast(this.layoutDoc.stroke_bezier),
- !closed ? 'none' : !isInkMask && (fillColor === 'transparent' || suppressFill) ? 'none' : fillColor,
+ !closed || !fillColor || DashColor(fillColor).alpha() === 0 ? 'none' : fillColor,
'',
'',
markerScale,
@@ -456,7 +471,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
cursor: this.props.isSelected() ? 'default' : undefined,
}}
{...interactions}>
- {clickableLine(this.onPointerDown, undefined, isInkMask)}
+ {clickableLine(this.onPointerDown, isInkMask)}
{isInkMask ? null : inkLine}
</svg>
{!closed || (!RTFCast(this.rootDoc.text)?.Text && (!this.props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 286d39943..afb76b9ac 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -55,7 +55,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
if (this._savedState.panY !== undefined) this.LightboxDoc._freeform_panY = this._savedState.panY;
if (this._savedState.scrollTop !== undefined) this.LightboxDoc._layout_scrollTop = this._savedState.scrollTop;
if (this._savedState.scale !== undefined) this.LightboxDoc._freeform_scale = this._savedState.scale;
- this.LightboxDoc.layout_fieldKey = this._savedState.layout_fieldKey;
+ this.LightboxDoc.layout_fieldKey = this._savedState.layout_fieldKey ? this._savedState.layout_fieldKey : undefined;
}
if (!doc) {
this._childFilters && (this._childFilters.length = 0);
@@ -313,8 +313,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
className="lightboxView-tabBtn"
title="open in tab"
onClick={e => {
+ const lightdoc = LightboxView._docTarget || LightboxView._doc!;
e.stopPropagation();
- CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none);
+ Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc);
+ CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none);
SelectionManager.DeselectAll();
LightboxView.SetLightboxDoc(undefined);
}}>
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index c7a7614ac..a403a10e3 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -11,6 +11,7 @@ body {
height: 100%;
overflow: hidden;
font-family: $sans-serif;
+ font-size: $body-text;
margin: 0;
position: absolute;
top: 0;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 1aeb93d42..730a926a2 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -6,20 +6,19 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { AssignAllExtensions } from '../../extensions/General/Extensions';
import { FieldLoader } from '../../fields/FieldLoader';
-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';
import * as dotenv from 'dotenv'; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import { BranchingTrailManager } from '../util/BranchingTrailManager';
+import { PingManager } from '../util/PingManager';
+import './global/globalScripts';
dotenv.config();
AssignAllExtensions();
-FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure why this is needed to get the code loaded properly...
+FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; // bcz: not sure why this is needed to get the code loaded properly...
(async () => {
MainView.Live = window.location.search.includes('live');
@@ -27,8 +26,12 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure
root.render(<FieldLoader />);
window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
const info = await CurrentUserUtils.loadCurrentUser();
- if (info.email === 'guest') DocServer.Control.makeReadOnly();
- await CurrentUserUtils.loadUserDocument(info.id);
+ // if (info.email === 'guest') DocServer.Control.makeReadOnly();
+ if (!info.userDocumentId) {
+ alert('Fatal Error: user not found in database');
+ return;
+ }
+ await CurrentUserUtils.loadUserDocument(info);
setTimeout(() => {
document.getElementById('root')!.addEventListener(
'wheel',
@@ -46,10 +49,10 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure
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();
new BranchingTrailManager();
+ new PingManager();
root.render(<MainView />);
}, 0);
})();
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index b95ce0e99..b3faff442 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -47,6 +47,10 @@ h1,
align-items: center;
justify-content: space-between;
gap: 10px;
+ background: rgb(0, 0, 0);
+ border-radius: 8px;
+ padding-left: 5px;
+ padding-right: 5px;
}
.mainView-snapLines {
@@ -68,10 +72,6 @@ h1,
left: 0;
z-index: 1;
touch-action: none;
-
- .searchBox-container {
- background: $light-gray;
- }
}
.mainView-container,
@@ -118,10 +118,6 @@ h1,
background: $light-gray;
}
- .searchBox-container {
- background: $dark-gray;
- }
-
.contextMenu-cont,
.contextMenu-item {
background: $dark-gray;
@@ -198,10 +194,10 @@ h1,
left: 0;
position: absolute;
z-index: 2;
- background-color: $light-gray;
+ background-color: linen; //$light-gray;
.editable-title {
- background-color: $light-gray;
+ background-color: linen; //$light-gray;
}
}
}
@@ -250,7 +246,6 @@ h1,
.mainView-leftMenuPanel {
min-width: var(--menuPanelWidth);
- background-color: $dark-gray;
border-right: $standard-border;
.collectionStackingView {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b0e992cb6..e376c4fdf 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -4,7 +4,7 @@ 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 'browndash-components/dist/styles/global.min.css';
-import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
+import { action, computed, configure, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import 'normalize.css';
import * as React from 'react';
@@ -21,7 +21,7 @@ import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
-import { ReportManager } from '../util/ReportManager';
+import { ReportManager } from '../util/reportManager/ReportManager';
import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SelectionManager } from '../util/SelectionManager';
@@ -153,47 +153,35 @@ export class MainView extends React.Component {
}
this._sidebarContent.proto = undefined;
if (!MainView.Live) {
- DocServer.setPlaygroundFields([
+ DocServer.setLivePlaygroundFields([
'dataTransition',
'viewTransition',
- 'treeViewOpen',
- 'layout_showSidebar',
+ 'treeView_Open',
+ 'treeView_ExpandedView',
'carousel_index',
'itemIndex', // for changing slides in presentations
'layout_sidebarWidthPercent',
'layout_currentTimecode',
'layout_timelineHeightPercent',
+ 'layout_hideMinimap',
+ 'layout_showSidebar',
+ 'layout_scrollTop',
+ 'layout_fitWidth',
+ 'layout_curPage',
'presStatus',
'freeform_panX',
'freeform_panY',
+ 'freeform_scale',
'overlayX',
'overlayY',
- 'layout_fitWidth',
- 'nativeWidth',
- 'nativeHeight',
'text_scrollHeight',
'text_height',
- 'layout_hideMinimap',
- 'freeform_scale',
- 'layout_scrollTop',
'hidden',
- 'layout_curPage',
- 'type_collection',
+ //'type_collection',
'chromeHidden',
'currentFrame',
- 'width',
- 'height',
- '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)
- )
- );
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
@@ -218,7 +206,7 @@ export class MainView extends React.Component {
window.removeEventListener('keydown', KeyManager.Instance.handle);
window.removeEventListener('pointerdown', this.globalPointerDown, true);
window.removeEventListener('pointermove', this.globalPointerMove, true);
- window.removeEventListener('mouseclick', this.globalPointerClick, true);
+ window.removeEventListener('pointerup', this.globalPointerClick, true);
window.removeEventListener('paste', KeyManager.Instance.paste as any);
document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
}
@@ -488,6 +476,8 @@ export class MainView extends React.Component {
fa.faSquareRootAlt,
fa.faVolumeMute,
fa.faUserCircle,
+ fa.faHeart,
+ fa.faHeartBroken,
fa.faHighlighter,
fa.faRemoveFormat,
fa.faHandPointUp,
@@ -529,12 +519,13 @@ export class MainView extends React.Component {
});
initEventListeners = () => {
+ window.addEventListener('beforeunload', DocServer.UPDATE_SERVER_CACHE);
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, true);
document.addEventListener('pointermove', this.globalPointerMove, true);
- document.addEventListener('mouseclick', this.globalPointerClick, true);
+ document.addEventListener('pointerup', this.globalPointerClick, true);
document.addEventListener(
'click',
(e: MouseEvent) => {
@@ -570,7 +561,7 @@ export class MainView extends React.Component {
@action
createNewFolder = async () => {
- const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true });
+ const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
};
@@ -581,6 +572,8 @@ export class MainView extends React.Component {
waitForDoubleClick = () => (this._exploreMode ? 'never' : undefined);
headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
+ addHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+ removeHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
@computed get headerBarDocView() {
return (
<div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -588,18 +581,19 @@ export class MainView extends React.Component {
key="headerBarDoc"
Document={this.headerBarDoc}
DataDoc={undefined}
- addDocument={undefined}
addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={DefaultStyleProvider}
rootSelected={returnTrue}
- removeDocument={returnFalse}
+ addDocument={this.addHeaderDoc}
+ removeDocument={this.removeHeaderDoc}
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}
+ childDragAction="move"
dontRegisterView={true}
hideResizeHandles={true}
PanelWidth={this.headerBarDocWidth}
@@ -694,15 +688,13 @@ export class MainView extends React.Component {
sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => {
- const whereFields = doc._type_collection === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
+ const whereFields = location.split(':');
const keyValue = whereFields[1]?.includes('KeyValue');
const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
- if (doc.dockingConfig) return DashboardView.openDashboard(doc);
+ if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
// prettier-ignore
switch (whereFields[0]) {
case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location);
- case OpenWhere.dashboard: return DashboardView.openDashboard(doc);
- case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods);
case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods);
case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue);
@@ -748,7 +740,7 @@ export class MainView extends React.Component {
@computed get leftMenuPanel() {
return (
- <div key="menu" className="mainView-leftMenuPanel" style={{ display: LightboxView.LightboxDoc ? 'none' : undefined }}>
+ <div key="menu" className="mainView-leftMenuPanel" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), display: LightboxView.LightboxDoc ? 'none' : undefined }}>
<DocumentView
Document={Doc.MyLeftSidebarMenu}
DataDoc={undefined}
@@ -803,14 +795,17 @@ export class MainView extends React.Component {
{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
+ className="mainView-libraryHandle"
+ style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
+ onPointerDown={this.onFlyoutPointerDown}>
+ <FontAwesomeIcon icon="chevron-left" color={StrCast(Doc.UserDoc().userColor)} style={{ opacity: '50%' }} size="sm" />
</div>
<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 }}>
+ <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1, background: 'linen' }}>
<FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
</div>
)}
@@ -865,7 +860,7 @@ export class MainView extends React.Component {
this._leftMenuFlyoutWidth = 0;
});
- remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+ remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && 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.MyDockedBtns, 'data', doc), true);
@@ -877,7 +872,7 @@ export class MainView extends React.Component {
@computed get docButtons() {
return !Doc.MyDockedBtns ? null : (
- <div className="mainView-docButtons" ref={this._docBtnRef}>
+ <div className="mainView-docButtons" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }} ref={this._docBtnRef}>
<CollectionLinearView
Document={Doc.MyDockedBtns}
DataDoc={undefined}
@@ -958,7 +953,11 @@ export class MainView extends React.Component {
render() {
return (
<div
- className={`mainView-container${this.colorScheme}`}
+ className={`mainView-container ${this.colorScheme}`}
+ style={{
+ color: StrCast(Doc.UserDoc().userColor),
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
+ }}
onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
ref={r => {
r &&
@@ -1013,6 +1012,7 @@ export class MainView extends React.Component {
<InkTranscription />
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+ {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
</div>
);
}
diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss
index 0648e31c5..4bf9eb79f 100644
--- a/src/client/views/MainViewModal.scss
+++ b/src/client/views/MainViewModal.scss
@@ -4,22 +4,23 @@
z-index: 10000;
width: 100%;
height: 100%;
+ box-shadow: #00000044 5px 5px 10px;
.dialogue-box {
- padding: 10px;
position: absolute;
z-index: 1000;
text-align: center;
justify-content: center;
align-self: center;
align-content: center;
- background: white;
// border-radius: 10px;
box-shadow: #00000044 5px 5px 10px;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
transition: 0.5s all ease;
+ border-radius: 10px;
+ overflow: hidden;
}
.overlay {
@@ -28,6 +29,7 @@
position: absolute;
z-index: 999;
transition: 0.5s all ease;
+ backdrop-filter: blur(5px);
}
} \ No newline at end of file
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 32997a944..42df99864 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,6 +1,10 @@
import * as React from 'react';
import './MainViewModal.scss';
import { observer } from 'mobx-react';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
+import { isDark } from 'browndash-components';
+import { Colors } from './global/globalEnums';
export interface MainViewOverlayProps {
isDisplayed: boolean;
@@ -41,9 +45,8 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
className="overlay"
onClick={this.props?.closeOnExternalClick}
style={{
- backgroundColor: 'black',
+ backgroundColor: isDark(StrCast(Doc.UserDoc().userColor)) ? "#DFDFDF30" : "#32323230",
...(p.overlayStyle || {}),
- opacity: p.isDisplayed ? overlayOpacity : 0,
}}
/>
</div>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a4a2c1df9..3d8d569fa 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -167,9 +167,9 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
annotationOn: this.props.rootDoc,
text: this.props.selectionText(),
backgroundColor: 'transparent',
- presDuration: 2100,
- presTransition: 500,
- presZoomText: true,
+ presentation_duration: 2100,
+ presentation_transition: 500,
+ presentation_zoomText: true,
title: 'Selection on ' + this.props.rootDoc.title,
});
let minX = Number.MAX_VALUE;
@@ -201,7 +201,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
// mainAnnoDocProto.text = this._selectionText;
- textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs);
+ textRegionAnnoProto.text_inlineAnnotations = new List<Doc>(annoDocs);
savedAnnoMap.clear();
return textRegionAnno;
};
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 033cdf1f7..5362bf9f0 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -5,7 +5,6 @@
width: 100vw;
height: 100vh;
/* background-color: pink; */
- z-index: 100000;
}
.overlayWindow-outerDiv {
@@ -55,8 +54,8 @@
}
.overlayView-doc {
- z-index: 9002; //so that it appears above chroma
+ 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/Palette.scss b/src/client/views/Palette.scss
deleted file mode 100644
index 0ec879288..000000000
--- a/src/client/views/Palette.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-.palette-container {
- .palette-thumb {
- touch-action: pan-x;
- position: absolute;
- height: 70px;
- overflow: hidden;
-
- .palette-thumbContent {
- transition: transform .3s;
- width: max-content;
- overflow: hidden;
-
- .collectionView {
- overflow: visible;
-
- .collectionLinearView-outer {
- overflow: visible;
- }
- }
- }
-
- .palette-cover {
- width: 50px;
- height: 50px;
- position: absolute;
- bottom: 0;
- border: 1px solid black;
- }
- }
-} \ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
deleted file mode 100644
index 749eb08a2..000000000
--- a/src/client/views/Palette.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { IReactionDisposer, observable, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../fields/Doc';
-import { NumCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from '../../Utils';
-import { Transform } from '../util/Transform';
-import { DocumentView } from './nodes/DocumentView';
-import './Palette.scss';
-
-export interface PaletteProps {
- x: number;
- y: number;
- thumb: number[];
- thumbDoc: Doc;
-}
-
-@observer
-export default class Palette extends React.Component<PaletteProps> {
- private _selectedDisposer?: IReactionDisposer;
- @observable private _selectedIndex: number = 0;
-
- componentDidMount = () => {
- this._selectedDisposer = reaction(
- () => NumCast(this.props.thumbDoc.selectedIndex),
- i => (this._selectedIndex = i),
- { fireImmediately: true }
- );
- };
-
- componentWillUnmount = () => {
- this._selectedDisposer?.();
- };
-
- render() {
- return (
- <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
- <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
- <div className="palette-thumbContent" style={{ transform: `translate(-${this._selectedIndex * 50 + 10}px, 0px)` }}>
- <DocumentView
- Document={this.props.thumbDoc}
- DataDoc={undefined}
- addDocument={undefined}
- addDocTab={returnFalse}
- rootSelected={returnTrue}
- pinToPres={emptyFunction}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={() => window.screen.width}
- PanelHeight={() => window.screen.height}
- renderDepth={0}
- isDocumentActive={returnTrue}
- isContentActive={emptyFunction}
- focus={emptyFunction}
- docViewPath={returnEmptyDoclist}
- styleProvider={returnEmptyString}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- childFilters={returnEmptyFilter}
- childFiltersByRanges={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- />
- <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index b513fe245..82d2bff56 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -50,18 +50,20 @@ export class PreviewCursor extends React.Component<{}> {
PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end);
} else if (re.test(plain)) {
const url = plain;
- undoBatch(() =>
- PreviewCursor._addDocument(
- Docs.Create.WebDocument(url, {
- title: url,
- _width: 500,
- _height: 300,
- data_useCors: true,
- x: newPoint[0],
- y: newPoint[1],
- })
- )
- )();
+ if (url.startsWith(window.location.href)) {
+ undoBatch(() =>
+ PreviewCursor._addDocument(
+ Docs.Create.WebDocument(url, {
+ title: url,
+ _width: 500,
+ _height: 300,
+ data_useCors: true,
+ x: newPoint[0],
+ y: newPoint[1],
+ })
+ )
+ )();
+ } else alert('cannot paste dash into itself');
} else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
const clone = plain.startsWith('__DashCloneId(');
const docids = plain.split(':');
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index 36b2df73e..b801b3abf 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -20,22 +20,31 @@ $linkGap : 3px;
.propertiesButtons-linkButton-empty,
.propertiesButtons-linkButton-nonempty {
- height: 25px;
- width: 29px;
- border-radius: 6px;
- pointer-events: auto;
- background-color: $dark-gray;
- color: #fcfbf7;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
+ // margin-right: 7px;
+ // margin-left: 8px;
+ height: 28px;
+ // width: 226px;//29px;
display: flex;
- justify-content: center;
align-items: center;
- margin-right: 10px;
- margin-left: 4px;
+ // height: 25px;
+ // width: 230px;//29px;
+ // display: flex;
+ // align-items: center;
+ // border-radius: 6px;
+ pointer-events: auto;
+ // background-color: $dark-gray;
+ // color: #fcfbf7;
+ // text-transform: uppercase;
+ // letter-spacing: 2px;
+ // font-size: 75%;
+ transition: transform 0.2s;
+ // text-align: center;
+
+
+ // justify-content: center;
+
+ // margin-right: 10px;
+ // margin-left: 4px;
&:hover {
background: $medium-gray;
@@ -46,25 +55,34 @@ $linkGap : 3px;
.propertiesButtons-linkButton-empty.toggle-on {
background-color: $medium-blue;
color: $white;
+ width:100%
}
.propertiesButtons-linkButton-empty.toggle-hover {
background-color: $light-blue;
color: $black;
+ width:100%
}
.propertiesButtons-linkButton-empty.toggle-off {
- background-color: $dark-gray;
- color: white;
+ background-color: white;//$dark-gray;
+ color: black; //white;
+ width:100%
+}
+
+.propertiesButtons-icon {
+ margin-left:8px;
}
.propertiesButtons {
- margin-top: 3px;
- grid-column: 1/4;
+ position:relative;
width: 100%;
- height: auto;
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- padding-bottom: 5.5px;
+ // margin-top: 3px;
+// // grid-column: 1/4;
+// width: 100%;
+// height: auto;
+// display: flex;
+// // flex-direction: row;
+// // flex-wrap: wrap;
+// padding-bottom: 5.5px;
}
.onClickFlyout-editScript {
@@ -80,8 +98,8 @@ $linkGap : 3px;
.propertiesButtons-button {
pointer-events: auto;
- padding-right: 5px;
- width: 25px;
+ padding-right: 8px;//5px;
+ width: 100%;//width: 25px;
border-radius: 5px;
margin-right: 20px;
margin-bottom: 8px;
@@ -110,18 +128,24 @@ $linkGap : 3px;
}
}
-.propertiesButtons-title {
- background: $dark-gray;
- color: $white;
- font-size: 6px;
- width: 37px;
- padding: 3px;
- height: 12px;
- border-radius: 7px;
+.propertiesButtons-label {
text-transform: uppercase;
- text-align: center;
- margin-top: -4px;
-}
+ margin-left: 8px;
+ // margin-right: 50 px;
+}
+
+// .propertiesButtons-title {
+// background: pink; //$dark-gray;
+// color: $white;
+// font-size: 6px;
+// width: 37px;
+// padding: 3px;
+// height: 12px;
+// border-radius: 7px;
+// text-transform: uppercase;
+// text-align: center;
+// margin-top: -4px;
+// }
.propertiesButtons-linker {
height: 25px;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 11b89fd69..8cae34d7d 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import { Icon, Tooltip } from '@material-ui/core';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
@@ -21,6 +21,17 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView';
import { pasteImageBitmap } from './nodes/WebBoxRenderer';
import './PropertiesButtons.scss';
import React = require('react');
+import { JsxElement } from 'typescript';
+import { FaBraille, FaHighlighter, FaLock, FaLockOpen, FaThumbtack } from 'react-icons/fa';
+import { AiOutlineApple, AiOutlineColumnWidth, AiOutlinePicture } from 'react-icons/ai';
+import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtitles, MdSubtitlesOff, MdTouchApp } from 'react-icons/md';
+import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
+import { BiHide, BiShow } from 'react-icons/bi';
+import { BsGrid3X3GapFill } from 'react-icons/bs';
+import { TfiBarChart } from 'react-icons/tfi';
+import { CiGrid31 } from 'react-icons/ci';
+import { RxWidth } from 'react-icons/rx';
+import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -30,6 +41,7 @@ enum UtilityButtonState {
OpenRight,
OpenExternally,
}
+
@observer
export class PropertiesButtons extends React.Component<{}, {}> {
@observable public static Instance: PropertiesButtons;
@@ -41,242 +53,291 @@ export class PropertiesButtons extends React.Component<{}, {}> {
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) => {
+ propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, 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 : (
- <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'}`}
- onPointerDown={e => e.stopPropagation()}
- onClick={undoable(() => {
- if (SelectionManager.Views().length > 1) {
- SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property));
- } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
- }, property)}>
- <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon={icon(BoolCast(targetDoc?.[property])) as any} />
- </div>
- <div className="propertiesButtons-title">{label}</div>
- </div>
- </Tooltip>
+ <Toggle
+ toggleStatus={BoolCast(targetDoc[property])}
+ text={label(targetDoc?.[property])}
+ color={StrCast(Doc.UserDoc().userColor)}
+ icon={icon(targetDoc?.[property] as any)}
+ iconPlacement={'left'}
+ align={'flex-start'}
+ fillWidth={true}
+ toggleType={ToggleType.BUTTON}
+ onClick={undoable(() => {
+ if (SelectionManager.Views().length > 1) {
+ SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property));
+ } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
+ }, property)}
+ />
);
};
+
+ // this implments a container pattern by marking the targetDoc (collection) as a lightbox
+ // that always fits its contents to its container and that hides all other documents when
+ // a link is followed that targets a 'lightbox' destination
+ @computed get isLightboxButton() {
+ return this.propertyToggleBtn(
+ on => 'Lightbox',
+ 'isLightbox',
+ on => `${on ? 'Set' : 'Remove'} lightbox flag`,
+ on => 'window-restore',
+ onClick => {
+ SelectionManager.Views().forEach(dv => {
+ const containerDoc = dv.rootDoc;
+ //containerDoc.followAllLinks =
+ // containerDoc.noShadow =
+ // containerDoc.layout_disableBrushing =
+ // containerDoc._forceActive =
+ //containerDoc._freeform_fitContentsToBox =
+ containerDoc._isLightbox = !containerDoc._isLightbox;
+ //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
+ const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
+ //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
+ });
+ }
+ );
+ }
+
+ @computed get titleButton() {
+ return this.propertyToggleBtn(
+ on => (!on ? 'SHOW TITLE' : this.selectedDoc?.['_layout_showTitle'] === 'title:hover' ? 'HIDE TITLE' : 'HOVER TITLE'),
+ '_layout_showTitle',
+ on => 'Switch between title styles',
+ on => (on ? <MdSubtitlesOff /> : <MdSubtitles />), // {currentIcon}, //(on ? <MdSubtitles/> :) , //,'text-width', on ? <MdSubtitles/> : <MdSubtitlesOff/>,
+ (dv, doc) => {
+ const tdoc = dv?.rootDoc || doc;
+ const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : '';
+ tdoc._layout_showTitle = newtitle ? newtitle : undefined;
+ }
+ );
+ }
+
@computed get lockButton() {
return this.propertyToggleBtn(
- 'No\xA0Drag',
+ on => (on ? 'UNLOCK' : 'LOCK'), // 'No\xA0Drag',
'_lockedPosition',
on => `${on ? 'Unlock' : 'Lock'} position to prevent dragging`,
- on => 'thumbtack'
+ on => (on ? <FaLockOpen /> : <FaLock />)
+ // on => 'thumbtack'
);
}
+
@computed get maskButton() {
+ //highlight text while going down and reading through
return this.propertyToggleBtn(
- 'Mask',
+ on => (on ? 'PLAIN INK' : 'HIGHLIGHTER MASK'),
'stroke_isInkMask',
on => (on ? 'Make plain ink' : 'Make highlight mask'),
- on => 'paint-brush',
+ on => (on ? <TbHighlightOff /> : <TbHighlight />), // <FaHighlighter/>,// 'paint-brush',
(dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc)
);
}
+
@computed get hideImageButton() {
+ // put in developer -- can trace on top of object and drawing is still there
return this.propertyToggleBtn(
- 'Background',
+ on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), //'Background',
'_hideImage',
on => (on ? 'Show Image' : 'Show Background'),
- on => 'portrait'
+ on => (on ? <BiShow /> : <BiHide />) //'portrait'
);
}
+
@computed get clustersButton() {
return this.propertyToggleBtn(
- 'Clusters',
+ on => (on ? 'DISABLE CLUSTERS' : 'HIGHLIGHT CLUSTERS'),
'_freeform_useClusters',
on => `${on ? 'Hide' : 'Show'} clusters`,
- on => 'braille'
+ on => <FaBraille />
);
}
@computed get panButton() {
return this.propertyToggleBtn(
- 'Lock\xA0View',
+ on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), //'Lock\xA0View',
'_lockedTransform',
on => `${on ? 'Unlock' : 'Lock'} panning of view`,
- on => 'lock'
+ on => (on ? <TbHandStop /> : <TbHandOff />) //'lock'
);
}
+
@computed get forceActiveButton() {
+ //select text
return this.propertyToggleBtn(
- 'Active',
+ on => (on ? 'INACTIVE INTERACTION' : 'ACTIVE INTERACTION'),
'_forceActive',
on => `${on ? 'Select to activate' : 'Contents always active'} `,
- on => 'eye'
+ on => <MdTouchApp /> // 'eye'
);
}
+
@computed get fitContentButton() {
return this.propertyToggleBtn(
- 'View All',
+ on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All',
'_freeform_fitContentsToBox',
on => `${on ? "Don't" : 'Do'} fit content to container visible area`,
- on => 'object-group'
- );
- }
- // this implments a container pattern by marking the targetDoc (collection) as a lightbox
- // that always fits its contents to its container and that hides all other documents when
- // a link is followed that targets a 'lightbox' destination
- @computed get isLightboxButton() {
- return this.propertyToggleBtn(
- 'Lightbox',
- 'isLightbox',
- on => `${on ? 'Set' : 'Remove'} lightbox flag`,
- on => 'window-restore',
- onClick => {
- SelectionManager.Views().forEach(dv => {
- const containerDoc = dv.rootDoc;
- //containerDoc.followAllLinks =
- // containerDoc.noShadow =
- // containerDoc.layout_disableBrushing =
- // containerDoc._forceActive =
- //containerDoc._freeform_fitContentsToBox =
- containerDoc._isLightbox = !containerDoc._isLightbox;
- //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
- const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
- //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
- containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
- });
- }
+ on => (on ? <CiGrid31 /> : <BsGrid3X3GapFill />) //'object-group'
);
}
+
+ // // this implments a container pattern by marking the targetDoc (collection) as a lightbox
+ // // that always fits its contents to its container and that hides all other documents when
+ // // a link is followed that targets a 'lightbox' destination
+ // @computed get isLightboxButton() { // developer
+ // return this.propertyToggleBtn(
+ // on => 'Lightbox',
+ // 'isLightbox',
+ // on => `${on ? 'Set' : 'Remove'} lightbox flag`,
+ // on => 'window-restore',
+ // onClick => {
+ // SelectionManager.Views().forEach(dv => {
+ // const containerDoc = dv.rootDoc;
+ // //containerDoc.followAllLinks =
+ // // containerDoc.noShadow =
+ // // containerDoc.disableDocBrushing =
+ // // containerDoc._forceActive =
+ // //containerDoc._freeform_fitContentsToBox =
+ // containerDoc._isLightbox = !containerDoc._isLightbox;
+ // //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
+ // const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
+ // //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ // containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
+ // });
+ // }
+ // );
+ // }
+
@computed get layout_fitWidthButton() {
return this.propertyToggleBtn(
- 'Fit\xA0Width',
+ on => (on ? 'RESTRICT WIDTH' : 'FIT WIDTH'), //'Fit\xA0Width',
'_layout_fitWidth',
on => `${on ? "Don't" : 'Do'} fit content to width of container`,
- on => 'arrows-alt-h'
+ on => (on ? <AiOutlineColumnWidth /> : <RxWidth />) // 'arrows-alt-h'
);
}
+
@computed get captionButton() {
return this.propertyToggleBtn(
- 'Caption',
+ //DEVELOPER
+ on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), //'Caption',
'_layout_showCaption',
on => `${on ? 'Hide' : 'Show'} caption footer`,
- on => 'closed-captioning',
+ on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), //'closed-captioning',
(dv, doc) => ((dv?.rootDoc || doc)._layout_showCaption = (dv?.rootDoc || doc)._layout_showCaption === undefined ? 'caption' : undefined)
);
}
+
@computed get chromeButton() {
+ // developer -- removing UI decoration
return this.propertyToggleBtn(
- 'Controls',
+ on => (on ? 'ENABLE UI CONTROLS' : 'DISABLE UI CONTROLS'),
'_chromeHidden',
on => `${on ? 'Show' : 'Hide'} editing UI`,
- on => 'edit',
+ on => (on ? <TbEditCircle /> : <TbEditCircleOff />), // 'edit',
(dv, doc) => ((dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden)
);
}
- @computed get titleButton() {
- return this.propertyToggleBtn(
- 'Title',
- '_layout_showTitle',
- on => 'Switch between title styles',
- on => 'text-width',
- (dv, doc) => {
- const tdoc = dv?.rootDoc || doc;
- const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : '';
- tdoc._layout_showTitle = newtitle;
- }
- );
- }
+
@computed get layout_autoHeightButton() {
+ // store previous dimensions to store old values
return this.propertyToggleBtn(
- 'Auto\xA0Size',
+ on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'),
'_layout_autoHeight',
on => `Automatical vertical sizing to show all content`,
- on => 'arrows-alt-v'
+ on => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
);
}
+
@computed get gridButton() {
return this.propertyToggleBtn(
- 'Grid',
+ on => (on ? 'HIDE GRID' : 'DISPLAY GRID'),
'_freeform_backgroundGrid',
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;
- }
- );
- }
- @computed get freezeThumb() {
- return this.propertyToggleBtn(
- 'FreezeThumb',
- '_thumb-frozen',
- on => `${on ? 'Freeze' : 'Unfreeze'} thumbnail`,
- on => 'snowflake',
- (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 && Utils.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename)));
- })
- );
- }
- }
+ on => (on ? <MdGridOff /> : <MdGridOn />) //'border-all'
);
}
+
+ // @computed get groupButton() { //developer
+ // return this.propertyToggleBtn(
+ // on => '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(
+ // 'FreezeThumb',
+ // '_thumb-frozen',
+ // on => `${on ? 'Freeze' : 'Unfreeze'} thumbnail`,
+ // on => 'snowflake',
+ // (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 && Utils.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename)));
+ // })
+ // );
+ // }
+ // }
+ // );
+ // }
@computed get snapButton() {
+ // THESE ARE NOT COMING
return this.propertyToggleBtn(
- 'Snap\xA0Lines',
+ on => (on ? 'HIDE SNAP LINES' : 'SHOW SNAP LINES'),
'freeform_snapLines',
on => `Display snapping lines when objects are dragged`,
- on => 'th',
- undefined,
- true
+ on => <TfiBarChart />, //'th',
+ undefined
);
}
- @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>
- </div>
- <div className="propertiesButtons-title"> onclick </div>
- </div>
- </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>
- </div>
- <div className="propertiesButtons-title"> Perspective </div>
- </div>
- </Tooltip>
- );
- }
+ // @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>
+ // </div>
+ // <div className="propertiesButtons-title"> onclick </div>
+ // </div>
+ // </Tooltip>
+ // );
+ // }
+ // @computed
+ // get perspectiveButton() { // gone
+ // 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>
+ // </Tooltip>
+ // );
+ // }
@undoBatch
handlePerspectiveChange = (e: any) => {
@@ -286,6 +347,60 @@ export class PropertiesButtons extends React.Component<{}, {}> {
.map(dv => dv.docView!)
.forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
};
+ @computed get onClickVal() {
+ const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
+ const followLoc = this.selectedDoc._followLinkLocation;
+ const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+
+ if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace';
+ else if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
+ else if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal';
+ else if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail';
+ else return 'nothing';
+ }
+
+ @computed
+ get onClickButton() {
+ const buttonList = [
+ ['nothing', 'Select Document'],
+ ['enterPortal', 'Enter Portal'],
+ ['toggleDetail', 'Toggle Detail'],
+ ['linkInPlace', 'Open Link in Lightbox'],
+ ['linkOnRight', 'Open Link on Right'],
+ ];
+
+ const items: IListItemProps[] = buttonList.map(value => {
+ return {
+ text: value[1],
+ val: value[1],
+ };
+ });
+ return !this.selectedDoc ? null : (
+ <Dropdown
+ tooltip={'Choose onClick behavior'}
+ items={items}
+ selectedVal={this.onClickVal}
+ setSelectedVal={val => this.handleOptionChange(val as string)}
+ title={'Choose onClick behaviour'}
+ color={StrCast(Doc.UserDoc().userColor)}
+ dropdownType={DropdownType.SELECT}
+ type={Type.SEC}
+ fillWidth
+ />
+ // <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>
+ // </Tooltip>
+ );
+ }
@undoBatch
@action
@@ -365,26 +480,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
</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?._type_collection ?? '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() {
const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
@@ -410,22 +505,21 @@ export class PropertiesButtons extends React.Component<{}, {}> {
{toggle(this.titleButton)}
{toggle(this.captionButton)}
{toggle(this.lockButton)}
- {toggle(this.onClickButton)}
+ {/* {toggle(this.onClickButton)} */}
{toggle(this.layout_fitWidthButton)}
- {toggle(this.freezeThumb)}
+ {/* {toggle(this.freezeThumb)} */}
{toggle(this.forceActiveButton)}
{toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
- {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
+ {/* {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} */}
{toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
{toggle(this.maskButton, { display: !isInk ? 'none' : '' })}
{toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })}
{toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })}
{toggle(this.gridButton, { display: !isCollection ? 'none' : '' })}
- {toggle(this.groupButton, { display: isTabView || !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>
);
}
diff --git a/src/client/views/PropertiesDocBacklinksSelector.scss b/src/client/views/PropertiesDocBacklinksSelector.scss
index 4d2a61c3b..5c53acf48 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.scss
+++ b/src/client/views/PropertiesDocBacklinksSelector.scss
@@ -1,4 +1,4 @@
-.propertiesView-contexts-content {
+.propertiesDocBacklinksSelector {
.linkMenu {
position: relative;
}
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 3e69bcba6..da4438481 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -31,7 +31,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
render() {
return !SelectionManager.Views().length ? null : (
- <div className="preroptiesDocBacklinksSelector">
+ <div className="propertiesDocBacklinksSelector">
{this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
<LinkMenu docView={SelectionManager.Views().lastElement()} clearLinkEditor={undefined} itemHandler={this.getOnClick} style={{ left: 0, top: 0 }} />
</div>
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index e1279c9a7..d157e7b1c 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -3,11 +3,10 @@ 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 { Cast, StrCast } from '../../fields/Types';
import { DocFocusOrOpen } from '../util/DocumentManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
-import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
import './PropertiesDocContextSelector.scss';
type PropertiesDocContextSelectorProps = {
@@ -39,6 +38,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
}, new Set<Doc>())
.keys()
);
+
return doclayouts
.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
.filter(doc => !Doc.IsSystem(doc))
@@ -53,6 +53,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
};
render() {
+ if (this._docs.length < 1) return undefined;
return (
<div>
{this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
diff --git a/src/client/views/PropertiesSection.scss b/src/client/views/PropertiesSection.scss
new file mode 100644
index 000000000..321b6300c
--- /dev/null
+++ b/src/client/views/PropertiesSection.scss
@@ -0,0 +1,24 @@
+@import './global/globalCssVariables.scss';
+
+.propertiesView-section {
+
+ .propertiesView-content {
+ padding: 10px;
+ }
+
+ .propertiesView-sectionTitle {
+ text-align: center;
+ display: flex;
+ padding: 3px 10px;
+ font-size: 14px;
+ font-weight: bold;
+ justify-content: space-between;
+ align-items: center;
+
+ .propertiesView-sectionTitle-icon {
+ width: 20px;
+ height: 20px;
+ align-items: flex-end;
+ }
+ }
+}
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
new file mode 100644
index 000000000..6fab0168b
--- /dev/null
+++ b/src/client/views/PropertiesSection.tsx
@@ -0,0 +1,64 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import './PropertiesSection.scss';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
+
+export interface PropertiesSectionProps {
+ title: string;
+ content?: JSX.Element | string | null;
+ isOpen: boolean;
+ setIsOpen: (bool: boolean) => any;
+ inSection?: boolean;
+ setInSection?: (bool: boolean) => any;
+ onDoubleClick?: () => void;
+}
+
+@observer
+export class PropertiesSection extends React.Component<PropertiesSectionProps> {
+ @computed get color() {
+ return StrCast(Doc.UserDoc().userColor);
+ }
+
+ @computed get backgroundColor() {
+ return StrCast(Doc.UserDoc().userBackgroundColor);
+ }
+
+ @computed get variantColor() {
+ return StrCast(Doc.UserDoc().userVariantColor);
+ }
+
+ @observable isDouble: boolean = false;
+
+ render() {
+ if (this.props.content === undefined || this.props.content === null) return null;
+ else
+ return (
+ <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}>
+ <div
+ className="propertiesView-sectionTitle"
+ onDoubleClick={action(e => {
+ this.isDouble = true;
+ this.props.onDoubleClick && this.props.onDoubleClick();
+ this.props.setIsOpen(true);
+ setTimeout(() => (this.isDouble = false), 300);
+ })}
+ onClick={action(e => {
+ this.props.setIsOpen(!this.props.isOpen);
+ })}
+ style={{
+ background: this.props.isOpen ? this.variantColor : this.backgroundColor,
+ color: this.color,
+ }}>
+ {this.props.title}
+ <div className="propertiesView-sectionTitle-icon">
+ <FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" />
+ </div>
+ </div>
+ {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.content}</div>}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 897be9a32..a0b054851 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -1,162 +1,122 @@
@import './global/globalCssVariables.scss';
+.propertiesView-presentationTrails-title {
+ display: flex;
+}
+.propertiesView-presentationTrails-title-icon {
+ position: absolute;
+ right: 4;
+}
.propertiesView {
height: 100%;
width: 250;
font-family: 'Roboto';
font-size: 12px;
cursor: auto;
+ border-left: $standard-border;
.slider-text {
font-size: 8px;
}
- overflow-x: hidden;
- overflow-y: auto;
-
.propertiesView-title {
- text-align: center;
- padding-top: 12px;
- padding-bottom: 12px;
- display: flex;
- font-size: 18px;
+ padding: 10px;
+ font-size: 24px;
font-weight: bold;
- justify-content: center;
-
- .propertiesView-title-icon {
- width: 20px;
- height: 20px;
- padding-left: 38px;
- margin-top: -5px;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 10px;
-
- &:hover {
- color: grey;
- cursor: pointer;
- }
- }
}
- .propertiesView-name {
- border-bottom: 1px solid black;
- padding: 8.5px;
- font-size: 12.5px;
+ overflow-x: hidden;
+ overflow-y: auto;
- &:hover {
- cursor: text;
- }
+ .propertiesView-name,
+ .propertiesView-type {
+ padding: 5px 10px;
}
- .propertiesView-settings {
+ .propertiesView-sharing {
//border-bottom: 1px solid black;
//padding: 8.5px;
- font-size: 12.5px;
- font-weight: bold;
- .propertiesView-settings-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
+ .propertiesView-buttonContainer {
+ float: right;
display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
- .propertiesView-settings-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
+ button {
+ width: 15;
+ height: 15;
+ padding: 0;
+ margin-top: -5;
}
}
- .propertiesView-settings-content {
- margin-left: 12px;
- padding-bottom: 10px;
- padding-top: 8px;
- }
- }
-
- .propertiesView-sharing {
- //border-bottom: 1px solid black;
- //padding: 8.5px;
-
- .propertiesView-sharing-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
+ .change-buttons {
display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
- &:hover {
- cursor: pointer;
+ button {
+ width: 5;
+ height: 5;
}
- .propertiesView-sharing-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
+ input {
+ width: 100%;
}
}
+ }
- .propertiesView-sharing-content {
- font-size: 10px;
- padding: 10px;
- margin-left: 5px;
-
- .propertiesView-buttonContainer {
- float: right;
- display: flex;
-
- button {
- width: 15;
- height: 15;
- padding: 0;
- margin-top: -5;
- }
-
- .propertiesView-acls-checkbox {
- margin-top: -20px;
-
- .propertiesView-acls-checkbox-text {
- font-size: 7px;
- margin-top: -10px;
- margin-left: 6px;
- }
- }
- }
-
- .change-buttons {
- display: flex;
+ .propertiesView-acls-checkbox {
+ float: right;
+ margin-left: 50px;
+ }
- button {
- width: 5;
- height: 5;
- }
+ .propertiesView-shareDropDown {
+ margin-right: 10px;
+ min-width: 65px;
- input {
- width: 100%;
- }
- }
+ & .propertiesView-shareDropDownNone {
+ padding: 0px;
+ padding-left: 3px;
+ padding-right: 3px;
+ background: grey;
+ color: rgb(71, 71, 71);
+ border-radius: 6px;
+ border: 1px solid rgb(71, 71, 71);
+ }
+ & .propertiesView-shareDropDownEdit,
+ .propertiesView-shareDropDownAdmin {
+ padding: 0px;
+ padding-left: 3px;
+ padding-right: 3px;
+ background: rgb(254, 254, 199);
+ color: rgb(75, 75, 5);
+ border-radius: 6px;
+ border: 1px solid rgb(75, 75, 5);
+ }
+ & .propertiesView-shareDropDownAugment {
+ padding: 0px;
+ padding-left: 3px;
+ padding-right: 3px;
+ background: rgb(208, 255, 208);
+ color: rgb(19, 80, 19);
+ border-radius: 6px;
+ border: 1px solid rgb(19, 80, 19);
+ }
+ & .propertiesView-shareDropDownView {
+ padding: 0px;
+ padding-left: 3px;
+ padding-right: 3px;
+ background: rgb(213, 213, 255);
+ color: rgb(25, 25, 101);
+ border-radius: 6px;
+ border: 1px solid rgb(25, 25, 101);
+ }
+ & .propertiesView-shareDropDownNot-Shared {
+ padding: 0px;
+ padding-left: 3px;
+ padding-right: 3px;
+ background: rgb(255, 207, 207);
+ color: rgb(138, 47, 47);
+ border-radius: 6px;
+ border: 1px solid rgb(138, 47, 47);
}
}
@@ -164,33 +124,7 @@
//border-bottom: 1px solid black;
//padding: 8.5px;
- .propertiesView-filters-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
- display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
-
- .propertiesView-filters-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .propertiesView-filters-content {
+ .propertiesView-content {
font-size: 10px;
padding: 10px;
margin-left: 5px;
@@ -219,85 +153,10 @@
}
}
- .propertiesView-appearance {
- //border-bottom: 1px solid black;
- //padding: 8.5px;
-
- .propertiesView-appearance-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
- display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
-
- .propertiesView-appearance-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .propertiesView-appearance-content {
- font-size: 10px;
- padding: 10px;
- margin-left: 5px;
- }
- }
-
- .propertiesView-transform {
- //border-bottom: 1px solid black;
- //padding: 8.5px;
-
- .propertiesView-transform-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
- display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
-
- .propertiesView-transform-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .propertiesView-transform-content {
- font-size: 10px;
- padding: 10px;
- margin-left: 5px;
- }
- }
-
.notify-button {
padding: 2px;
width: 12px;
height: 12px;
- background-color: black;
border-radius: 10px;
padding-left: 2px;
padding-right: 2px;
@@ -317,19 +176,7 @@
}
.expansion-button {
- margin-left: -20;
-
- .expansion-button-icon {
- width: 11px;
- height: 11px;
- color: black;
- margin-left: 27px;
-
- &:hover {
- color: rgb(131, 131, 131);
- cursor: pointer;
- }
- }
+ margin-right: 10px;
}
.propertiesView-sharingTable {
@@ -340,18 +187,13 @@
padding: 5px; // remove when adding buttons
border-radius: 6px; // remove when adding buttons
margin-right: 10px; // remove when adding buttons
- // width: 100%;
- // display: inline-table;
background-color: #ececec;
- max-height: 130px;
- overflow-y: auto;
- width: 92%;
+ width: 97%;
.propertiesView-sharingTable-item {
display: flex;
- // padding: 5px;
padding: 3px;
- align-items: center;
+ align-items: right;
border-bottom: 0.5px solid grey;
&:hover .propertiesView-sharingTable-item-name {
@@ -372,19 +214,9 @@
.propertiesView-sharingTable-item-permission {
display: flex;
align-items: flex-end;
+ text-align: right;
margin-left: auto;
-
- .permissions-select {
- border: none;
- background-color: inherit;
- width: 87px;
- text-align: justify; // for Edge
- text-align-last: end;
-
- &:hover {
- cursor: pointer;
- }
- }
+ margin-right: -12px;
}
&:last-child {
@@ -393,57 +225,9 @@
}
}
- .propertiesView-fields {
- //border-bottom: 1px solid black;
- //padding: 8.5px;
-
- .propertiesView-fields-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
- display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
-
- .propertiesView-fields-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .propertiesView-fields-checkbox {
- float: right;
- height: 20px;
- margin-top: -9px;
-
- .propertiesView-fields-checkbox-text {
- font-size: 7px;
- margin-top: -10px;
- margin-left: 6px;
- }
- }
-
- .propertiesView-fields-content {
- font-size: 10px;
- margin-left: 2px;
- padding: 10px;
-
- &:hover {
- cursor: pointer;
- }
- }
+ .propertiesView-permissions-select {
+ background: inherit;
+ border: none;
}
.propertiesView-field {
@@ -465,109 +249,11 @@
cursor: auto;
}
}
+}
- .propertiesView-contexts {
- .propertiesView-contexts-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
- display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
-
- .propertiesView-contexts-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .propertiesView-contexts-content {
- overflow: hidden;
- padding: 10px;
- }
- }
-
- .propertiesView-layout {
- .propertiesView-layout-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
- display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
-
- .propertiesView-layout-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .propertiesView-layout-content {
- overflow: hidden;
- padding: 10px;
- }
- }
-
- .propertiesView-presTrails {
- //border-bottom: 1px solid black;
- //padding: 8.5px;
-
- .propertiesView-presTrails-title {
- font-weight: bold;
- font-size: 12.5px;
- padding: 4px;
- display: flex;
- color: white;
- padding-left: 8px;
- background-color: rgb(51, 51, 51);
-
- &:hover {
- cursor: pointer;
- }
-
- .propertiesView-presTrails-title-icon {
- float: right;
- justify-items: right;
- align-items: flex-end;
- margin-left: auto;
- margin-right: 9px;
-
- &:hover {
- cursor: pointer;
- }
- }
- }
-
- .propertiesView-presTrails-content {
- font-size: 10px;
- padding: 10px;
- margin-left: 5px;
- }
- }
+.propertiesView-presentationTrails {
+ //border-bottom: 1px solid black;
+ //padding: 8.5px;
}
.inking-button {
@@ -830,8 +516,16 @@
}
}
+.propertiesView-wordTitle {
+ color: darkslategray;
+ font-weight: 200;
+}
+
.editable-title {
border: solid 1px #323232;
+ padding-left: 5px;
+ padding-top: 4px;
+ border-radius: 4px;
height: fit-content;
&:hover {
@@ -839,6 +533,26 @@
}
}
+.propertiesView-wordType {
+ color: darkslategray;
+ font-weight: 200;
+}
+
+.currentType {
+ text-decoration: underline;
+ display: flex;
+ align-items: center;
+ // border: solid 1px #323232;
+ // padding-left: 5px;
+ // padding-top: 4px;
+ // border-radius: 4px;
+ // height: fit-content;
+}
+
+.currentType-icon {
+ margin-right: 5px;
+}
+
.properties-flyout {
grid-column: 2/4;
}
@@ -847,10 +561,6 @@
padding: 5%;
}
-.propertiesView-section {
- padding-left: 20px;
-}
-
.propertiesView-input {
padding: 4px 8px;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 1f2e21dd5..82ab54787 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -3,38 +3,43 @@ import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight, faWindowMaximize } 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, computed, Lambda, observable } from 'mobx';
+import { Button, Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
+import { concat } from 'lodash';
+import { Lambda, action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState, SketchPicker } from 'react-color';
-import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, StrListCast } from '../../fields/Doc';
+import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
+import { GrCircleInformation } from 'react-icons/gr';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
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 { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
import { DocumentManager } from '../util/DocumentManager';
+import { GroupManager } from '../util/GroupManager';
import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
import { SharingManager } from '../util/SharingManager';
import { Transform } from '../util/Transform';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../util/UndoManager';
import { EditableView } from './EditableView';
import { FilterPanel } from './FilterPanel';
-import { Colors } from './global/globalEnums';
import { InkStrokeProperties } from './InkStrokeProperties';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
import { PropertiesButtons } from './PropertiesButtons';
import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector';
import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
+import { PropertiesSection } from './PropertiesSection';
import './PropertiesView.scss';
import { DefaultStyleProvider } from './StyleProvider';
+import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
+import { SettingsManager } from '../util/SettingsManager';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -186,7 +191,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
});
rows.push(
- <div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px' }}>
+ <div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px', backgroundColor: 'white', textAlign: 'center' }}>
<EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} />
</div>
);
@@ -244,11 +249,30 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this.props.addDocTab} />;
}
+ @computed get contextCount() {
+ if (this.selectedDocumentView) {
+ const target = this.selectedDocumentView.props.Document;
+ const embeddings = DocListCast(target.proto_embeddings);
+ return embeddings.length - 1;
+ } else {
+ return 0;
+ }
+ }
+
@computed get links() {
const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this.props.addDocTab} />;
}
+ @computed get linkCount() {
+ const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
+ var counter = 0;
+
+ LinkManager.Links(selAnchor).forEach((l, i) => counter++);
+
+ return counter;
+ }
+
@computed get layoutPreview() {
if (SelectionManager.Views().length > 1) {
return '-- multiple selected --';
@@ -287,7 +311,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
pinToPres={emptyFunction}
bringToFront={returnFalse}
dontRegisterView={true}
- dropAction={undefined}
/>
</div>
);
@@ -302,26 +325,22 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@undoBatch
changePermissions = (e: any, user: string) => {
const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc! : DocCast(doc)[DocData]));
- SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs);
+ SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls);
};
/**
* @returns the options for the permissions dropdown.
*/
- getPermissionsSelect(user: string, permission: string) {
- const dropdownValues: string[] = Object.values(SharingPermissions);
+ getPermissionsSelect(user: string, permission: string, showGuestOptions: boolean) {
+ const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
if (permission === '-multiple-') dropdownValues.unshift(permission);
- if (user !== 'Override') dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
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 className="propertiesView-permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}>
+ {dropdownValues.map(permission => (
+ <option className="propertiesView-permisssions-select" key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ))}
</select>
);
}
@@ -333,7 +352,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
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" />
+ <FontAwesomeIcon className="notify-button-icon" icon="bell" size="sm" />
</div>
</Tooltip>
);
@@ -344,17 +363,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
*/
@computed get expansionIcon() {
return (
- <Tooltip title={<div className="dash-tooltip">Show more permissions</div>}>
- <div
- className="expansion-button"
- onPointerDown={() => {
+ <div className="expansion-button">
+ <IconButton
+ icon={<FontAwesomeIcon icon={'ellipsis-h'} />}
+ size={Size.XSMALL}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={action(() => {
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>
+ })}
+ />
+ </div>
);
}
@@ -362,6 +382,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
* @returns a row of the permissions panel
*/
sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
+ if (name == Doc.CurrentUserEmail) {
+ name = 'Me';
+ }
return (
<div
className="propertiesView-sharingTable-item"
@@ -375,90 +398,208 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</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}
+ {this.colorACLDropDown(name, admin, permission, false)}
+ {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null}
</div>
</div>
);
}
/**
+ * @returns a colored dropdown bar reflective of the permission
+ */
+ colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) {
+ var shareImage = ReverseHierarchyMap.get(permission)?.image;
+ return (
+ <div>
+ <div className={'propertiesView-shareDropDown'}>
+ <div className={`propertiesView-shareDropDown${permission}`}>
+ <div>{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}</div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ /**
+ * Sorting algorithm to sort users.
+ */
+ sortUsers = (u1: String, u2: String) => {
+ return u1 > u2 ? -1 : u1 === u2 ? 0 : 1;
+ };
+
+ /**
+ * Sorting algorithm to sort groups.
+ */
+ sortGroups = (group1: Doc, group2: Doc) => {
+ const g1 = StrCast(group1.title);
+ const g2 = StrCast(group2.title);
+ return g1 > g2 ? -1 : g1 === g2 ? 0 : 1;
+ };
+
+ /**
* @returns the sharing and permissions panel.
*/
@computed get sharingTable() {
// all selected docs
- const docs =
- SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutDocAcls ? this.selectedDoc : this.dataDoc!] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DocData]));
-
+ const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
const target = docs[0];
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
- const showAdmin = effectiveAcls.every(acl => acl === AclAdmin);
+ const showAdmin = GetEffectiveAcl(target) == AclAdmin;
+ const individualTableEntries = [];
+ const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
+
+ const seldoc = this.layoutDocAcls || !this.selectedDoc ? this.selectedDoc : Doc.GetProto(this.selectedDoc);
+ // adds each user to usersAdded
+ SharingManager.Instance.users.forEach(eachUser => {
+ var userOnDoc = true;
+ if (seldoc) {
+ if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) {
+ userOnDoc = false;
+ }
+ }
+ if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) {
+ usersAdded.push(eachUser.user.email);
+ }
+ });
- // users in common between all docs
- const commonKeys: string[] = intersection(...docs.map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl]).filter(key => key !== 'acl-Me')));
+ // sorts and then adds each user to the table
+ usersAdded.sort(this.sortUsers);
+ usersAdded.map(userEmail => {
+ const userKey = `acl-${normalizeEmail(userEmail)}`;
+ var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true);
+ var permission = StrCast(aclField);
+ individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
+ });
- const tableEntries = [];
+ // adds current user
+ var userEmail = Doc.CurrentUserEmail;
+ if (userEmail == 'guest') userEmail = 'Guest';
+ const userKey = `acl-${normalizeEmail(userEmail)}`;
+ if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) {
+ var permission;
+ if (this.layoutDocAcls) {
+ if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name;
+ else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[userKey]);
+ else permission = StrCast(Doc.GetProto(target)?.[userKey]);
+ } else permission = StrCast(target[userKey]);
+ individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
+ }
- // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
- if (commonKeys.length) {
- for (const key of commonKeys) {
- const name = denormalizeEmail(key.substring(4));
- const uniform = docs.every(doc => doc?.[DocAcl]?.[key] === docs[0]?.[DocAcl]?.[key]);
- if (name !== Doc.CurrentUserEmail && name !== target.author && name !== 'Public' && name !== 'Override' /* && sidebarUsersDisplayed![name] !== false*/) {
- tableEntries.push(this.sharingItem(name, showAdmin, uniform ? HierarchyMapping.get(target[DocAcl][key])!.name : '-multiple-'));
+ // shift owner to top
+ individualTableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false);
+
+ // adds groups
+ const groupTableEntries: JSX.Element[] = [];
+ const groupList = GroupManager.Instance?.allGroups || [];
+ groupList.sort(this.sortGroups);
+ groupList.map(group => {
+ if (group.title != 'Guest' && this.selectedDoc) {
+ const groupKey = 'acl-' + normalizeEmail(StrCast(group.title));
+ if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) {
+ var permission;
+ if (this.layoutDocAcls) {
+ if (target[DocAcl][groupKey]) {
+ permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name;
+ } else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]);
+ else permission = StrCast(Doc.GetProto(target)?.[groupKey]);
+ } else permission = StrCast(target[groupKey]);
+ groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false));
}
}
- }
+ });
- 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-"));
- if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'));
- tableEntries.unshift(this.sharingItem('Public', showAdmin, StrCast(docs.filter(doc => doc).every(doc => doc['acl-Public'] === target['acl-Public']) ? target['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]) ? HierarchyMapping.get(effectiveAcls[0])!.name : '-multiple-',
- !ownerSame
- )
- );
+ // guest permission
+ const guestPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']);
- return <div className="propertiesView-sharingTable">{tableEntries}</div>;
+ return (
+ <div>
+ <div>
+ <br></br> Individuals with Access to this Document
+ </div>
+ <div className="propertiesView-sharingTable" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
+ {<div> {individualTableEntries}</div>}
+ </div>
+ {groupTableEntries.length > 0 ? (
+ <div>
+ <div>
+ <br></br> Groups with Access to this Document
+ </div>
+ <div className="propertiesView-sharingTable" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
+ {<div> {groupTableEntries}</div>}
+ </div>
+ </div>
+ ) : null}
+ <br></br> Guest
+ <div>{this.colorACLDropDown('Guest', showAdmin, guestPermission!, true)}</div>
+ </div>
+ );
}
@computed get fieldsCheckbox() {
+ // color= "primary"
return <Checkbox color="primary" onChange={this.toggleCheckbox} checked={this.layoutFields} />;
}
@action
toggleCheckbox = () => (this.layoutFields = !this.layoutFields);
+ @computed get color() {
+ return StrCast(Doc.UserDoc().userColor);
+ }
+
+ @computed get backgroundColor() {
+ return StrCast(Doc.UserDoc().userBackgroundColor);
+ }
+
+ @computed get variantColor() {
+ return StrCast(Doc.UserDoc().userVariantColor);
+ }
+
@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);
+ SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
+ return <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />;
+ }
+
+ @computed get currentType() {
+ const documentType = StrCast(this.selectedDoc?.type);
+ var currentType: string = documentType;
+ var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType);
+
return (
- <div className="editable-title">
- <EditableView key="editableView" contents={title} height={25} fontSize={14} GetValue={() => title} SetValue={this.setTitle} />
+ <div>
+ <div className="propertiesView-wordType">Type</div>
+ <div className="currentType">
+ <div className="currentType-icon">{this.currentComponent}</div>
+
+ {capitalizedDocType}
+ </div>
</div>
);
}
+ @computed get currentComponent() {
+ var iconName = StrCast(this.selectedDoc?.systemIcon);
+
+ if (iconName) {
+ const Icon = Icons[iconName as keyof typeof Icons];
+ return <Icon />;
+ } else {
+ return <Icons.BsFillCollectionFill />;
+ }
+ }
+
@undoBatch
@action
- setTitle = (value: string) => {
+ setTitle = (value: string | number) => {
if (SelectionManager.Views().length > 1) {
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);
- return true;
+ else KeyValueBox.SetField(this.dataDoc, 'title', value as string, true);
}
- return false;
};
@undoBatch
@@ -516,7 +657,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
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" />
+ <FontAwesomeIcon icon="bezier-curve" size="lg" />
</div>
</Tooltip>
</div>
@@ -535,10 +676,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<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" />
+ <FontAwesomeIcon icon="caret-up" 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" />
+ <FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
</div>
@@ -792,16 +933,28 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<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" />
+ <FontAwesomeIcon icon="caret-up" 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" />
+ <FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
</div>
);
};
+ @action
+ onDoubleClick = () => {
+ this.openContexts = false;
+ this.openLinks = false;
+ this.openOptions = false;
+ this.openTransform = false;
+ this.openFields = false;
+ this.openSharing = false;
+ this.openLayout = false;
+ this.openFilters = false;
+ };
+
@computed get widthAndDash() {
return (
<div className="widthAndDash">
@@ -881,62 +1034,93 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
);
}
+ getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any) => {
+ return (
+ <div>
+ <NumberInput formLabel={label} formLabelPlacement={'left'} type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
+ <Slider multithumb={false} color={this.color} size={Size.XSMALL} min={min} max={max} unit={unit} number={number} setNumber={setNumber} fillWidth />
+ </div>
+ );
+ };
+
@computed get transformEditor() {
return (
<div className="transform-editor">
{this.isInk ? this.controlPointsButton : null}
- {this.hgtInput}
- {this.XpsInput}
+ {this.getNumber(
+ 'Height',
+ ' px',
+ 0,
+ 1000,
+ Number(this.shapeHgt),
+ undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height')
+ )}
+ {this.getNumber(
+ 'Width',
+ ' px',
+ 0,
+ 1000,
+ Number(this.shapeWid),
+ undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width')
+ )}
+ {this.getNumber(
+ 'X Coordinate',
+ ' px',
+ -2000,
+ 2000,
+ Number(this.shapeXps),
+ undoable((val: string) => !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord')
+ )}
+ {this.getNumber(
+ 'Y Coordinate',
+ ' px',
+ -2000,
+ 2000,
+ Number(this.shapeYps),
+ undoable((val: string) => !isNaN(Number(val)) && (this.shapeYps = val), 'set y coord')
+ )}
</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" />
- </div>
- </div>
- {!this.openOptions ? null : (
- <div className="propertiesView-settings-content">
- <PropertiesButtons />
- </div>
- )}
- </div>
+ <PropertiesSection
+ title="Options"
+ content={<PropertiesButtons />}
+ inSection={this.inOptions}
+ isOpen={this.openOptions}
+ setInSection={bool => (this.inOptions = bool)}
+ setIsOpen={bool => (this.openOptions = bool)}
+ onDoubleClick={() => this.onDoubleClick()}
+ />
);
}
@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" />
- </div>
- </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> */}
+ <PropertiesSection
+ title="Sharing & Permissions"
+ content={
+ <>
+ {/* <div className="propertiesView-buttonContainer"> */}
+ <div className="propertiesView-acls-checkbox">
+ Layout Permissions
+ <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
</div>
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
+ <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
+ <FontAwesomeIcon icon="redo-alt" size="1x" />
+ </button>
+ </Tooltip> */}
+ {/* </div> */}
{this.sharingTable}
- </div>
- )}
- </div>
+ </>
+ }
+ isOpen={this.openSharing}
+ setIsOpen={bool => (this.openSharing = bool)}
+ onDoubleClick={() => this.onDoubleClick()}
+ />
);
}
@@ -966,110 +1150,61 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get filtersSubMenu() {
return (
- <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>
- {!this.openFilters ? null : (
- <div className="propertiesView-filters-content" style={{ position: 'relative', height: 'auto' }}>
+ <PropertiesSection
+ title="Filters"
+ content={
+ <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
<FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} />
</div>
- )}
- </div>
+ }
+ isOpen={this.openFilters}
+ setIsOpen={bool => (this.openFilters = bool)}
+ onDoubleClick={() => this.onDoubleClick()}
+ />
);
}
@computed get inkSubMenu() {
+ let isDouble = false;
+
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>
- )}
-
- <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>
+ <PropertiesSection title="Appearance" content={this.isInk ? this.appearanceEditor : null} isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.onDoubleClick()} />
+ <PropertiesSection title="Transform" content={this.transformEditor} isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.onDoubleClick()} />
</>
);
}
@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" />
- </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>
+ <PropertiesSection
+ title="Fields & Tags"
+ content={<div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>}
+ isOpen={this.openFields}
+ setIsOpen={bool => (this.openFields = bool)}
+ onDoubleClick={() => this.onDoubleClick()}
+ />
);
}
@computed get contextsSubMenu() {
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>
+ <PropertiesSection
+ title="Other Contexts"
+ content={this.contextCount > 0 ? this.contexts : 'There are no other contexts.'}
+ isOpen={this.openContexts}
+ setIsOpen={bool => (this.openContexts = bool)}
+ onDoubleClick={() => this.onDoubleClick()}
+ />
);
}
@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>
- );
+ return <PropertiesSection title="Linked To" content={this.linkCount > 0 ? this.links : 'There are no current links.'} isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={() => this.onDoubleClick()} />;
}
@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" />
- </div>
- </div>
- {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null}
- </div>
- );
+ return <PropertiesSection title="Layout" content={this.layoutPreview} isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={() => this.onDoubleClick()} />;
}
@computed get description() {
@@ -1271,6 +1406,227 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
this.sourceAnchor && (this.sourceAnchor.followLinkZoomScale = scale);
};
+ @computed get linkProperties() {
+ const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
+ const targZoom = this.sourceAnchor?.followLinkZoom;
+ const indent = 30;
+ const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
+
+ return (
+ <>
+ <div className="propertiesView-section" style={{ background: 'darkgray' }}>
+ <div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}>
+ <p>Relationship</p>
+ {this.editRelationship}
+ </div>
+ <div className="propertiesView-input" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}>
+ <p>Description</p>
+ {this.editDescription}
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Show link</p>
+ <button
+ style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+ <p>Auto-move anchors</p>
+ <button
+ style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+ <p>Display arrow</p>
+ <button
+ style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ </div>
+ {!hasSelectedAnchor ? null : (
+ <div className="propertiesView-section">
+ <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}>
+ <p>Follow by</p>
+ <select onChange={e => this.changeFollowBehavior(e.currentTarget.value === 'Default' ? undefined : e.currentTarget.value)} value={Cast(this.sourceAnchor?.followLinkLocation, 'string', null)}>
+ <option value={undefined}>Default</option>
+ <option value={OpenWhere.addLeft}>Opening in new left pane</option>
+ <option value={OpenWhere.addRight}>Opening in new right pane</option>
+ <option value={OpenWhere.replaceLeft}>Replacing left tab</option>
+ <option value={OpenWhere.replaceRight}>Replacing right tab</option>
+ <option value={OpenWhere.lightbox}>Opening in lightbox</option>
+ <option value={OpenWhere.add}>Opening in new tab</option>
+ <option value={OpenWhere.replace}>Replacing current tab</option>
+ <option value={OpenWhere.inParent}>Opening in same collection</option>
+ {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
+ </select>
+ </div>
+ <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}>
+ <p>Animation</p>
+ <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}>
+ <option value="default">Default</option>
+ {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => (
+ <option key={effect.toString()} value={effect.toString()}>
+ {effect.toString()}
+ </option>
+ ))}
+ </select>
+ <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}>
+ {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})}
+ {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})}
+ {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})}
+ {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})}
+ {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
+ </div>
+ </div>
+ {PresBox.inputter(
+ '0.1',
+ '0.1',
+ '10',
+ NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000,
+ true,
+ (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)),
+ indent
+ )}{' '}
+ <div
+ className={'slider-headers'}
+ style={{
+ display: 'grid',
+ justifyContent: 'space-between',
+ width: `calc(100% - ${indent * 2}px)`,
+ marginLeft: indent,
+ marginRight: indent,
+ gridTemplateColumns: 'auto auto',
+ borderTop: 'solid',
+ }}>
+ <div className="slider-text">Fast</div>
+ <div className="slider-text">Slow</div>
+ </div>{' '}
+ <div className="propertiesView-input inline">
+ <p>Play Target Audio</p>
+ <button
+ style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Zoom Text Selections</p>
+ <button
+ style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Toggle Follow to Outer Context</p>
+ <button
+ style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Toggle Target (Show/Hide)</p>
+ <button
+ style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Ease Transitions</p>
+ <button
+ style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Capture Offset to Target</p>
+ <button
+ style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => {
+ this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined);
+ this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined);
+ }}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Center Target (no zoom)</p>
+ <button
+ style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
+ <p>Zoom %</p>
+ <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
+ <input className="presBox-input" style={{ width: '100%' }} readOnly={true} type="number" value={zoom} />
+ <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
+ <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
+ <FontAwesomeIcon icon={'caret-up'} />
+ </div>
+ <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
+ <FontAwesomeIcon icon={'caret-down'} />
+ </div>
+ </div>
+ </div>
+ <button
+ style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)}
+ <div
+ className={'slider-headers'}
+ style={{
+ display: !targZoom ? 'none' : 'grid',
+ justifyContent: 'space-between',
+ width: `calc(100% - ${indent * 2}px)`,
+ marginLeft: indent,
+ marginRight: indent,
+ gridTemplateColumns: 'auto auto',
+ borderTop: 'solid',
+ }}>
+ <div className="slider-text">0%</div>
+ <div className="slider-text">100%</div>
+ </div>{' '}
+ </div>
+ )}
+ </>
+ );
+ }
+
/**
* Handles adding and removing members from the sharing panel
*/
@@ -1284,9 +1640,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
render() {
const isNovice = Doc.noviceMode;
- const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
- const targZoom = this.sourceAnchor?.followLinkZoom;
- const indent = 30;
const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
if (!this.selectedDoc && !this.isPres) {
return (
@@ -1302,243 +1655,30 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div
className="propertiesView"
style={{
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
+ color: StrCast(Doc.UserDoc().userColor),
width: this.props.width,
minWidth: this.props.width,
- //overflowY: this.scrolling ? "scroll" : "visible"
}}>
- <div className="propertiesView-title" style={{ width: this.props.width }}>
- Properties
+ <div className="propertiesView-propAndInfoGrouping">
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ Properties
+ </div>
+ <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/')}>
+ <GrCircleInformation />{' '}
+ </div>
</div>
- <div className="propertiesView-name">{this.editableTitle}</div>
+ <div className="propertiesView-name">{this.editableTitle}</div>
+ <div className="propertiesView-type"> {this.currentType} </div>
{this.contextsSubMenu}
-
{this.linksSubMenu}
- {!this.selectedDoc || !LinkManager.currentLink || (!hasSelectedAnchor && this.selectedDoc !== LinkManager.currentLink) ? null : (
- <>
- <div className="propertiesView-section" style={{ background: 'darkgray' }}>
- <div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}>
- <p>Relationship</p>
- {this.editRelationship}
- </div>
- <div className="propertiesView-input" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}>
- <p>Description</p>
- {this.editDescription}
- </div>
- <div className="propertiesView-input inline">
- <p>Show link</p>
- <button
- style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
- <p>Auto-move anchors</p>
- <button
- style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
- <p>Display arrow</p>
- <button
- style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
- </div>
- {!hasSelectedAnchor ? null : (
- <div className="propertiesView-section">
- <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}>
- <p>Follow by</p>
- <select onChange={e => this.changeFollowBehavior(e.currentTarget.value === 'Default' ? undefined : e.currentTarget.value)} value={Cast(this.sourceAnchor?.followLinkLocation, 'string', null)}>
- <option value={undefined}>Default</option>
- <option value={OpenWhere.addLeft}>Opening in new left pane</option>
- <option value={OpenWhere.addRight}>Opening in new right pane</option>
- <option value={OpenWhere.replaceLeft}>Replacing left tab</option>
- <option value={OpenWhere.replaceRight}>Replacing right tab</option>
- <option value={OpenWhere.fullScreen}>Overlaying current tab</option>
- <option value={OpenWhere.lightbox}>Opening in lightbox</option>
- <option value={OpenWhere.add}>Opening in new tab</option>
- <option value={OpenWhere.replace}>Replacing current tab</option>
- <option value={OpenWhere.inParent}>Opening in same collection</option>
- {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
- </select>
- </div>
- <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}>
- <p>Animation</p>
- <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}>
- <option value="default">Default</option>
- {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => (
- <option key={effect.toString()} value={effect.toString()}>
- {effect.toString()}
- </option>
- ))}
- </select>
- <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}>
- {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})}
- {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})}
- {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})}
- {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})}
- {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
- </div>
- </div>
- {PresBox.inputter(
- '0.1',
- '0.1',
- '10',
- NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000,
- true,
- (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)),
- indent
- )}{' '}
- <div
- className={'slider-headers'}
- style={{
- display: 'grid',
- justifyContent: 'space-between',
- width: `calc(100% - ${indent * 2}px)`,
- marginLeft: indent,
- marginRight: indent,
- gridTemplateColumns: 'auto auto',
- borderTop: 'solid',
- }}>
- <div className="slider-text">Fast</div>
- <div className="slider-text">Slow</div>
- </div>{' '}
- <div className="propertiesView-input inline">
- <p>Play Target Audio</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline">
- <p>Zoom Text Selections</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline">
- <p>Toggle Follow to Outer Context</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline">
- <p>Toggle Target (Show/Hide)</p>
- <button
- style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline">
- <p>Ease Transitions</p>
- <button
- style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline">
- <p>Capture Offset to Target</p>
- <button
- style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => {
- this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined);
- this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined);
- }}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline">
- <p>Center Target (no zoom)</p>
- <button
- style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
- <p>Zoom %</p>
- <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
- <input className="presBox-input" style={{ width: '100%' }} readOnly={true} type="number" value={zoom} />
- <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
- <FontAwesomeIcon icon={'caret-up'} />
- </div>
- <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
- <FontAwesomeIcon icon={'caret-down'} />
- </div>
- </div>
- </div>
- <button
- style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
- onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
- {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)}
- <div
- className={'slider-headers'}
- style={{
- display: !targZoom ? 'none' : 'grid',
- justifyContent: 'space-between',
- width: `calc(100% - ${indent * 2}px)`,
- marginLeft: indent,
- marginRight: indent,
- gridTemplateColumns: 'auto auto',
- borderTop: 'solid',
- }}>
- <div className="slider-text">0%</div>
- <div className="slider-text">100%</div>
- </div>{' '}
- </div>
- )}
- </>
- )}
-
+ {!this.selectedDoc || !LinkManager.currentLink || (!hasSelectedAnchor && this.selectedDoc !== LinkManager.currentLink) ? null : this.linkProperties}
{this.inkSubMenu}
-
{this.optionsSubMenu}
-
{this.fieldsSubMenu}
-
{isNovice ? null : this.sharingSubMenu}
-
{isNovice ? null : this.filtersSubMenu}
-
{isNovice ? null : this.layoutSubMenu}
</div>
);
@@ -1550,7 +1690,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
: PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
return (
<div className="propertiesView" style={{ width: this.props.width }}>
- <div className="propertiesView-title" style={{ width: this.props.width }}>
+ <div className="propertiesView-sectionTitle" style={{ width: this.props.width }}>
Presentation
</div>
<div className="propertiesView-name" style={{ borderBottom: 0 }}>
@@ -1561,50 +1701,71 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
</div>
{!selectedItem ? null : (
- <div className="propertiesView-presTrails">
- <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))}
+ style={{
+ color: SettingsManager.Instance.userColor,
+ backgroundColor: this.openPresTransitions ? SettingsManager.Instance.userVariantColor : SettingsManager.Instance.userBackgroundColor,
+ }}>
&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-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openPresTransitions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+ {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
</div>
)}
{!selectedItem ? null : (
- <div className="propertiesView-presTrails">
+ <div className="propertiesView-presentationTrails">
<div
- className="propertiesView-presTrails-title"
+ className="propertiesView-presentationTrails-title"
onPointerDown={action(() => (this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration))}
- style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
+ style={{
+ color: SettingsManager.Instance.userColor,
+ backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.Instance.userVariantColor : SettingsManager.Instance.userBackgroundColor,
+ }}>
&nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Visibilty
- <div className="propertiesView-presTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openPresVisibilityAndDuration ? <div className="propertiesView-presTrails-content">{PresBox.Instance.visibiltyDurationDropdown}</div> : null}
+ {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibiltyDurationDropdown}</div> : null}
</div>
)}
{!selectedItem ? null : (
- <div className="propertiesView-presTrails">
- <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}>
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))}
+ style={{
+ color: SettingsManager.Instance.userColor,
+ backgroundColor: this.openPresProgressivize ? SettingsManager.Instance.userVariantColor : SettingsManager.Instance.userBackgroundColor,
+ }}>
&nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Progressivize
- <div className="propertiesView-presTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" color="white" />
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
+ {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{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' : '' }}>
+ <div className="propertiesView-presentationTrails">
+ <div
+ className="propertiesView-presentationTrails-title"
+ onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))}
+ style={{
+ color: SettingsManager.Instance.userColor,
+ backgroundColor: this.openSlideOptions ? SettingsManager.Instance.userVariantColor : SettingsManager.Instance.userBackgroundColor,
+ }}>
&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 className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openSlideOptions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
+ {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
</div>
)}
</div>
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 5dfe10def..2bc2d5e6b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -5,6 +5,7 @@ import * as React from 'react';
import { DocumentManager } from '../util/DocumentManager';
import { CompileScript, Transformer, ts } from '../util/Scripting';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { undoable } from '../util/UndoManager';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
import './ScriptingRepl.scss';
@@ -161,7 +162,7 @@ export class ScriptingRepl extends React.Component {
this.commands.push({ command: this.commandString, result: script.errors });
return;
}
- const result = script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() }));
+ const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
if (result.success) {
this.commands.push({ command: this.commandString, result: result.result });
this.commandsHistory.push(this.commandString);
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 80c878386..c06bb287e 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -31,8 +31,8 @@
.styleProvider-treeView-icon,
.styleProvider-treeView-icon-active {
- margin-left: 0.25rem;
- margin-right: 0.25rem;
+ margin-left: 0;
+ margin-right: 0;
}
.styleProvider-treeView-icon {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5eabf21fc..46243d50a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { Shadows } from 'browndash-components';
+import { IconButton, Shadows, Size } from 'browndash-components';
import { action, runInAction } from 'mobx';
import { extname } from 'path';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
@@ -22,12 +22,13 @@ import { DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { SliderBox } from './nodes/SliderBox';
+import { BsArrowDown, BsArrowUp, BsArrowDownUp } from 'react-icons/bs';
import './StyleProvider.scss';
import React = require('react');
export enum StyleProp {
- TreeViewIcon = 'treeViewIcon',
- TreeViewSortings = 'treeViewSortings', // options for how to sort tree view items
+ TreeViewIcon = 'treeView_Icon',
+ TreeViewSortings = 'treeView_Sortings', // 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
BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
@@ -43,7 +44,6 @@ export enum StyleProp {
ShowCaption = 'layout_showCaption',
TitleHeight = 'titleHeight', // Height of Title area
ShowTitle = 'layout_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', // font family of text
@@ -90,13 +90,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const isAnchor = property.includes(':anchor');
const isAnnotated = property.includes(':annotated');
const isOpen = property.includes(':open');
+ const isEmpty = property.includes(':empty');
const boxBackground = property.includes(':box');
const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
const layout_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);
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.TreeViewIcon:
@@ -106,15 +106,16 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
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);
+ return Doc.toIcon(doc, isEmpty ? undefined : 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?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', label: 'z' };
- allSorts[TreeSort.None] = { color: 'darkgray', label: '\u00A0\u00A0\u00A0' };
+ const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
+ allSorts[TreeSort.Down] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
+ allSorts[TreeSort.Up] = { color: 'crimson', icon: <BsArrowUp/> };
+ if (doc?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', icon: 'Z' };
+ allSorts[TreeSort.None] = { color: 'darkgray', icon: <BsArrowDownUp/> };
return allSorts;
case StyleProp.Highlighting:
+ if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;
if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
const selected = SelectionManager.Views().some(dv => dv.rootDoc === doc);
const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
@@ -132,7 +133,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
return undefined;
case StyleProp.DocContents:return undefined;
case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? 'lightgrey' : 'dimgrey';
- case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+ case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
case StyleProp.HideLinkBtn:return props?.hideLinkButton || (!selected && doc?.layout_hideLinkButton);
case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._text_fontSize, StrCast(Doc.UserDoc().fontSize)));
case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
@@ -143,7 +144,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case StyleProp.ShowTitle:
return (
(doc &&
- !doc.presentationTargetDoc &&
+ !doc.presentation_targetDoc &&
!props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
props?.layout_showTitle?.() !== '' &&
StrCast(
@@ -158,7 +159,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
''
);
case StyleProp.Color:
- if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
+ if (MainView.Instance.LastButton === doc) return SettingsManager.Instance.userBackgroundColor;
+ if (Doc.IsSystem(doc!)) return StrCast(Doc.UserDoc().userColor)
+ if (doc?.type === DocumentType.FONTICON) return Doc.UserDoc().userColor;
const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
if (docColor) return docColor;
const docView = props?.DocumentView?.();
@@ -183,8 +186,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
</div>
),
};
- case StyleProp.JitterRotation:
- return Doc.IsComicStyle(doc) ? 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?._type_collection as any) ||
(doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) ||
@@ -194,7 +195,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
? 15
: 0;
case StyleProp.BackgroundColor: {
- if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY;
+ if (MainView.Instance.LastButton === doc) return StrCast(Doc.UserDoc().userColor); // hack to indicate active menu panel item
let docColor: Opt<string> = StrCast(doc?.[fieldKey + '_backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
// prettier-ignore
switch (doc?.type) {
@@ -203,12 +204,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case DocumentType.PRES: docColor = docColor || (darkScheme() ? 'transparent' : 'transparent'); break;
case DocumentType.FONTICON: docColor = boxBackground ? undefined : 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?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break;
case DocumentType.EQUATION: docColor = docColor || 'transparent'; break;
case DocumentType.LABEL: docColor = docColor || (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:
@@ -219,14 +218,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case DocumentType.COL:
if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
docColor = docColor || (Doc.IsSystem(doc)
- ? darkScheme()
- ? Colors.DARK_GRAY
- : Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background
+ ? SettingsManager.Instance.userBackgroundColor
: doc.annotationOn
? '#00000010' // 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)'));
+ : doc._type_collection === CollectionViewType.Stacking ?
+ (darkScheme() ? Colors.MEDIUM_GRAY : Colors.DARK_GRAY)
+ : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : Colors.MEDIUM_GRAY));
break;
//if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE);
@@ -264,6 +263,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
case StyleProp.PointerEvents:
const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString;
+ if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
if (MainView.Instance._exploreMode || doc?.layout_unrendered) return isInk ? 'visiblePainted' : 'all';
if (props?.contentPointerEvents) return StrCast(props.contentPointerEvents);
@@ -276,7 +276,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
if (props?.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform) {
return doc?.pointerEvents !== 'none' ? null : (
<div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
- <FontAwesomeIcon icon={'lock'} style={{ color: 'red' }} size="lg" />
+ <FontAwesomeIcon icon='lock' style={{ color: 'red' }} size="lg" />
</div>
);
}
@@ -296,11 +296,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
};
const audio = () => {
const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
- const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations']).length;
+ const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length;
if (!doc || props?.renderDepth === -1 || (!audioAnnosCount(doc) && audioAnnoState(doc) === 'stopped')) return null;
const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
return (
- <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations-text']).lastElement()}</div>}>
+ <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
<div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.docView?.playAnnotation()}>
<FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
</div>
@@ -318,18 +318,19 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, offIcon: IconProp, clickFunc?: () => void) {
+ const color = StrCast(Doc.UserDoc().userColor);
return (
- <div
- title={field}
- className={`styleProvider-treeView-icon${doc[field] ? '-active' : ''}`}
+ <IconButton
+ size={Size.XSMALL}
+ color={color}
+ icon={<FontAwesomeIcon icon={(doc[field] ? (onIcon as any) : offIcon) as IconProp} />}
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>
+ )}
+ />
);
}
/**
@@ -337,7 +338,7 @@ export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp,
*/
export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
if (doc && property.split(':')[0] === StyleProp.Decorations) {
- return doc._type_collection === CollectionViewType.Docking
+ return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc)
? null
: DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.embedContainer ?? doc?.annotationOn)));
}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 62f47ba71..a3884c9eb 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -97,12 +97,12 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
TraceMobx();
const firstDoc = this.props.docViews[0].props.Document;
const templateName = StrCast(firstDoc.layout_fieldKey, '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 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={'default'} name={'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
- addedTypes.concat(noteTypes).map(template => (template.treeViewChecked = this.templateIsUsed(firstDoc, template)));
+ addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template)));
this._addedKeys &&
Array.from(this._addedKeys)
.filter(key => !noteTypes.some(nt => nt.title === key))
@@ -122,7 +122,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
rootSelected={returnFalse}
onCheckedClick={this.scriptField}
onChildClick={this.scriptField}
- dropAction={undefined}
isAnyChildContentActive={returnFalse}
isContentActive={returnTrue}
bringToFront={emptyFunction}
diff --git a/src/client/views/UndoStack.scss b/src/client/views/UndoStack.scss
index ab21e6d7e..192b99a12 100644
--- a/src/client/views/UndoStack.scss
+++ b/src/client/views/UndoStack.scss
@@ -2,9 +2,10 @@
height: 100%;
display: flex;
flex-direction: column;
+ justify-content: center;
+ align-items: center;
position: relative;
pointer-events: all;
- padding-left: 4px;
}
.undoStack-resultContainer {
@@ -21,9 +22,11 @@
}
.undoStack-commandsContainer {
- background-color: whitesmoke;
flex: 1 1 auto;
overflow-y: scroll;
- height: 30px;
+ height: fit-content;
+ width: 200px;
border-radius: 5px;
+ padding: 5px;
+ max-height: 200px;
}
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index f5af09e5b..a551e5332 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -3,6 +3,11 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { UndoManager } from '../util/UndoManager';
import './UndoStack.scss';
+import { StrCast } from '../../fields/Types';
+import { Doc } from '../../fields/Doc';
+import { Popup, Type, isDark } from 'browndash-components';
+import { Colors } from './global/globalEnums';
+import { SettingsManager } from '../util/SettingsManager';
interface UndoStackProps {
width?: number;
@@ -14,33 +19,39 @@ export class UndoStack extends React.Component<UndoStackProps> {
@observable static HideInline: boolean;
@observable static Expand: boolean;
render() {
+ const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.Instance.userBackgroundColor;
return this.props.inline && UndoStack.HideInline ? null : (
- <div
- className="undoStack-outerContainer"
- style={{ width: this.props.width, height: this.props.height ? (UndoStack.Expand ? 4 : 1) * this.props.height : undefined, top: UndoStack.Expand && this.props.height ? -this.props.height * 3 : undefined }}
- onClick={action(e => (UndoStack.Expand = !UndoStack.Expand))}
- onDoubleClick={action(e => (UndoStack.Expand = UndoStack.HideInline = false))}>
- <div className="undoStack-commandsContainer" ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })} style={{ background: UndoManager.batchCounter.get() ? 'yellow' : undefined }}>
- <div className="undoStack-resultContainer" key={0}>
- <div className="undoStack-commandString" style={{ fontWeight: 'bold', textAlign: 'center' }}>
- Undo/Redo Stack
- </div>
- </div>
- {UndoManager.undoStackNames.map((name, i) => (
- <div className="undoStack-resultContainer" key={i}>
- <div className="undoStack-commandString">{name.replace(/[^\.]*\./, '')}</div>
- </div>
- ))}
- {Array.from(UndoManager.redoStackNames)
- .reverse()
- .map((name, i) => (
- <div className="undoStack-resultContainer" key={i}>
- <div className="undoStack-commandString" style={{ fontWeight: 'bold', color: 'red' }}>
- {name.replace(/[^\.]*\./, '')}
+ <div className="undoStack-outerContainer">
+ <Popup
+ text={'Undo/Redo Stack'}
+ color={UndoManager.batchCounter.get() ? 'yellow' : StrCast(Doc.UserDoc().userVariantColor)}
+ placement={`top-start`}
+ type={Type.TERT}
+ popup={
+ <div
+ className="undoStack-commandsContainer"
+ ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })}
+ style={{
+ background: background,
+ color: isDark(background) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY,
+ }}>
+ {UndoManager.undoStackNames.map((name, i) => (
+ <div className="undoStack-resultContainer" key={i}>
+ <div className="undoStack-commandString">{name.replace(/[^\.]*\./, '')}</div>
</div>
- </div>
- ))}
- </div>
+ ))}
+ {Array.from(UndoManager.redoStackNames)
+ .reverse()
+ .map((name, i) => (
+ <div className="undoStack-resultContainer" key={i}>
+ <div className="undoStack-commandString" style={{ fontWeight: 'bold', color: 'red' }}>
+ {name.replace(/[^\.]*\./, '')}
+ </div>
+ </div>
+ ))}
+ </div>
+ }
+ />
</div>
);
}
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 3465a5283..addc00c85 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -123,7 +123,7 @@ export type RegionData = makeInterface<[typeof RegionDataSchema]>;
export const RegionData = makeInterface(RegionDataSchema);
interface IProps {
- node: Doc;
+ animatedDoc: Doc;
RegionData: Doc;
collection: Doc;
tickSpacing: number;
@@ -167,7 +167,7 @@ export class Keyframe extends React.Component<IProps> {
return RegionData(this.props.RegionData);
}
@computed private get regions() {
- return DocListCast(this.props.node.regions);
+ return DocListCast(this.props.animatedDoc.regions);
}
@computed private get keyframes() {
return DocListCast(this.regiondata.keyframes);
@@ -375,7 +375,7 @@ export class Keyframe extends React.Component<IProps> {
*/
@action
makeRegionMenu = (kf: Doc, e: MouseEvent) => {
- TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this.props.node.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)),
+ TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this.props.animatedDoc.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)),
TimelineMenu.Instance.addItem('input', `fadeIn: ${this.regiondata.fadeIn}ms`, val => {
runInAction(() => {
let cannotMove: boolean = false;
@@ -461,7 +461,7 @@ export class Keyframe extends React.Component<IProps> {
e.stopPropagation();
const div = ref.current!;
div.style.opacity = '1';
- Doc.BrushDoc(this.props.node);
+ Doc.BrushDoc(this.props.animatedDoc);
};
/**
@@ -473,7 +473,7 @@ export class Keyframe extends React.Component<IProps> {
e.stopPropagation();
const div = ref.current!;
div.style.opacity = '0';
- Doc.UnBrushDoc(this.props.node);
+ Doc.UnBrushDoc(this.props.animatedDoc);
};
///////////////////////UI STUFF /////////////////////////
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index adc97bbb4..7ca13756a 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -535,7 +535,7 @@ export class Timeline extends React.Component<FieldViewProps> {
{this.children.map(doc => (
<Track
ref={ref => this.mapOfTracks.push(ref)}
- node={doc}
+ animatedDoc={doc}
currentBarX={this._currentBarX}
changeCurrentBarX={this.changeCurrentBarX}
transform={this.props.ScreenToLocalTransform()}
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 2349ba786..1010332f5 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -12,7 +12,7 @@ import { Keyframe, KeyframeFunc, RegionData } from './Keyframe';
import './Track.scss';
interface IProps {
- node: Doc;
+ animatedDoc: Doc;
currentBarX: number;
transform: Transform;
collection: Doc;
@@ -36,23 +36,23 @@ export class Track extends React.Component<IProps> {
private objectWhitelist = ['data'];
@computed private get regions() {
- return DocListCast(this.props.node.regions);
+ return DocListCast(this.props.animatedDoc.regions);
}
@computed private get time() {
return NumCast(KeyframeFunc.convertPixelTime(this.props.currentBarX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
}
async componentDidMount() {
- const regions = await DocListCastAsync(this.props.node.regions);
- if (!regions) this.props.node.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
+ const regions = await DocListCastAsync(this.props.animatedDoc.regions);
+ if (!regions) this.props.animatedDoc.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
//these two lines are exactly same from timeline.tsx
const relativeHeight = window.innerHeight / 20;
runInAction(() => (this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT)); //for responsiveness
this._timelineVisibleReaction = this.timelineVisibleReaction();
this._currentBarXReaction = this.currentBarXReaction();
- if (DocListCast(this.props.node.regions).length === 0) this.createRegion(this.time);
- this.props.node.hidden = false;
- this.props.node.opacity = 1;
+ if (DocListCast(this.props.animatedDoc.regions).length === 0) this.createRegion(this.time);
+ this.props.animatedDoc.hidden = false;
+ this.props.animatedDoc.opacity = 1;
// this.autoCreateKeyframe();
}
@@ -127,13 +127,13 @@ export class Track extends React.Component<IProps> {
*/
@action
autoCreateKeyframe = () => {
- const objects = this.objectWhitelist.map(key => this.props.node[key]);
- intercept(this.props.node, change => {
+ const objects = this.objectWhitelist.map(key => this.props.animatedDoc[key]);
+ intercept(this.props.animatedDoc, change => {
return change;
});
return reaction(
() => {
- return [...this.primitiveWhitelist.map(key => this.props.node[key]), ...objects];
+ return [...this.primitiveWhitelist.map(key => this.props.animatedDoc[key]), ...objects];
},
(changed, reaction) => {
//check for region
@@ -171,14 +171,14 @@ export class Track extends React.Component<IProps> {
() => {
const regiondata = this.findRegion(this.time);
if (regiondata) {
- this.props.node.hidden = false;
+ this.props.animatedDoc.hidden = false;
// if (!this._autoKfReaction) {
// // this._autoKfReaction = this.autoCreateKeyframe();
// }
this.timeChange();
} else {
- this.props.node.hidden = true;
- this.props.node.opacity = 0;
+ this.props.animatedDoc.hidden = true;
+ this.props.animatedDoc.opacity = 0;
//if (this._autoKfReaction) this._autoKfReaction();
}
}
@@ -250,10 +250,10 @@ export class Track extends React.Component<IProps> {
private applyKeys = async (kf: Doc) => {
this.primitiveWhitelist.forEach(key => {
if (!kf[key]) {
- this.props.node[key] = undefined;
+ this.props.animatedDoc[key] = undefined;
} else {
const stored = kf[key];
- this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
+ this.props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
};
@@ -282,11 +282,11 @@ export class Track extends React.Component<IProps> {
const dif = NumCast(right[key]) - NumCast(left[key]);
const deltaLeft = this.time - NumCast(left.time);
const ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time));
- this.props.node[key] = NumCast(left[key]) + dif * ratio;
+ this.props.animatedDoc[key] = NumCast(left[key]) + dif * ratio;
} else {
// case data
const stored = left[key];
- this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
+ this.props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
};
@@ -326,7 +326,7 @@ export class Track extends React.Component<IProps> {
regiondata.duration = rightRegion.position - regiondata.position;
}
if (this.regions.length === 0 || !rightRegion || (rightRegion && rightRegion.position - regiondata.position >= NumCast(regiondata.fadeIn) + NumCast(regiondata.fadeOut))) {
- Cast(this.props.node.regions, listSpec(Doc))?.push(regiondata);
+ Cast(this.props.animatedDoc.regions, listSpec(Doc))?.push(regiondata);
this._newKeyframe = true;
this.saveStateRegion = regiondata;
return regiondata;
@@ -360,7 +360,7 @@ export class Track extends React.Component<IProps> {
@action
copyDocDataToKeyFrame = (doc: Doc) => {
this.primitiveWhitelist.map(key => {
- const originalVal = this.props.node[key];
+ const originalVal = this.props.animatedDoc[key];
doc[key] = originalVal instanceof ObjectField ? originalVal[Copy]() : originalVal;
});
};
@@ -377,8 +377,8 @@ export class Track extends React.Component<IProps> {
ref={this._inner}
style={{ height: `${this._trackHeight}px` }}
onDoubleClick={this.onInnerDoubleClick}
- onPointerOver={() => Doc.BrushDoc(this.props.node)}
- onPointerOut={() => Doc.UnBrushDoc(this.props.node)}>
+ onPointerOver={() => Doc.BrushDoc(this.props.animatedDoc)}
+ onPointerOut={() => Doc.UnBrushDoc(this.props.animatedDoc)}>
{this.regions?.map((region, i) => {
return <Keyframe key={`${i}`} {...this.props} RegionData={region} makeKeyData={this.makeKeyData} />;
})}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 0eb61a0b2..ea02bcd4c 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../../fields/Doc';
import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { returnFalse, returnZero, StopEvent } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero, StopEvent } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -60,6 +60,8 @@ export class CollectionCarouselView extends CollectionSubView() {
NativeHeight={returnZero}
onDoubleClick={this.onContentDoubleClick}
onClick={this.onContentClick}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.props.isContentActive}
+ isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction}
hideCaptions={show_captions ? true : false}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.childLayoutTemplate}
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 78e44dfa2..d93015506 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -15,6 +15,12 @@
cursor: grab;
color: $black;
}
+.collectiondockingview-container .lm_splitter {
+ opacity: 0.2;
+ &:hover {
+ opacity: 1;
+ }
+}
.lm_title.focus-visible {
-webkit-appearance: none;
@@ -89,6 +95,13 @@
position: relative;
}
+.lm_maximised .lm_header {
+ background-color: #000000;
+}
+.lm_maximised .lm_tab {
+ width: 100%;
+}
+
.lm_stack {
position: relative;
}
@@ -163,6 +176,7 @@
display: flex;
align-content: center;
justify-content: center;
+ background: $dark-gray;
}
.lm_controls > li {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e9cc2c894..0052c4196 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,7 +8,7 @@ import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { inheritParentAcls } from '../../../fields/util';
+import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
import { emptyFunction, incrementTitleCopy } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
@@ -28,6 +28,8 @@ import './CollectionDockingView.scss';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { TabDocView } from './TabDocView';
+import { DocumentManager } from '../../util/DocumentManager';
+import { AclAdmin, AclEdit } from '../../../fields/DocSymbols';
import React = require('react');
const _global = (window /* browser */ || global) /* node */ as any;
@@ -125,28 +127,6 @@ export class CollectionDockingView extends CollectionSubView() {
}
@undoBatch
- public static OpenFullScreen(doc: Doc) {
- SelectionManager.DeselectAll();
- const instance = CollectionDockingView.Instance;
- if (instance) {
- if (doc._type_collection === CollectionViewType.Docking && doc.layout_fieldKey === 'layout') {
- return DashboardView.openDashboard(doc);
- }
- const newItemStackConfig = {
- type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(Doc.MakeEmbedding(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();
- }
- return true;
- }
-
- @undoBatch
@action
public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean, keyValue?: boolean): boolean {
const instance = CollectionDockingView.Instance;
@@ -174,9 +154,7 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
- return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
- ? CollectionDockingView.CloseSplit(doc)
- : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue);
+ return Array.from(CollectionDockingView.Instance?.tabMap.keys() ?? []).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue);
}
//
@@ -184,9 +162,9 @@ export class CollectionDockingView extends CollectionSubView() {
//
@action
public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
- if (document?._type_collection === CollectionViewType.Docking) return DashboardView.openDashboard(document);
+ if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document);
if (!CollectionDockingView.Instance) return false;
- const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !keyValue);
+ const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !tab.contentItem.config.props.keyValue && !keyValue);
if (tab) {
tab.header.parent.setActiveContentItem(tab.contentItem);
return true;
@@ -361,6 +339,7 @@ export class CollectionDockingView extends CollectionSubView() {
} catch (e) {}
this._goldenLayout?.destroy();
window.removeEventListener('resize', this.onResize);
+ window.removeEventListener('mouseup', this.onPointerUp);
this._reactionDisposer?.();
this._lightboxReactionDisposer?.();
@@ -385,8 +364,13 @@ export class CollectionDockingView extends CollectionSubView() {
.map(f => f as Doc);
const changesMade = this.props.Document.dockingConfig !== json;
if (changesMade) {
- this.props.Document.dockingConfig = json;
- this.props.Document.data = new List<Doc>(docs);
+ if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) {
+ this.layoutDoc.dockingConfig = json;
+ this.layoutDoc.data = new List<Doc>(docs);
+ } else {
+ Doc.SetInPlace(this.rootDoc, 'dockingConfig', json, true);
+ Doc.SetInPlace(this.rootDoc, 'data', new List<Doc>(docs), true);
+ }
}
this._flush?.end();
this._flush = undefined;
@@ -411,7 +395,14 @@ export class CollectionDockingView extends CollectionSubView() {
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_maximise')) this._flush = UndoManager.StartBatch('tab maximize');
- else if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE();
+ else {
+ const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement);
+ const map = Array.from(this.tabMap).find(tab => tab.element[0] === tabTarget);
+ if (map?.DashDoc && DocumentManager.Instance.getFirstDocumentView(map.DashDoc)) {
+ SelectionManager.SelectView(DocumentManager.Instance.getFirstDocumentView(map.DashDoc), false);
+ }
+ if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE();
+ }
}
}
if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
@@ -453,7 +444,7 @@ export class CollectionDockingView extends CollectionSubView() {
const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc));
if (newtabdocs.length) {
Doc.GetProto(newtab).data = new List<Doc>(newtabdocs);
- newtabdocs.forEach(ntab => (ntab.embedContainer = newtab));
+ newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab));
}
json = json.replace(origtab[Id], newtab[Id]);
return newtab;
@@ -473,9 +464,12 @@ export class CollectionDockingView extends CollectionSubView() {
tabDestroyed = (tab: any) => {
this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
- if (tab.DashDoc && ![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) {
+ if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) {
Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
- Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
+ // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
+ if (tab.DashDoc.embedContainer === this.rootDoc) tab.DashDoc.embedContainer = undefined;
+ if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
+ Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc);
}
if (CollectionDockingView.Instance) {
const dview = CollectionDockingView.Instance.props.Document;
@@ -504,7 +498,7 @@ export class CollectionDockingView extends CollectionSubView() {
_layout_fitWidth: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
- this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+ inheritParentAcls(this.rootDoc, docToAdd, false);
CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
}
});
@@ -547,7 +541,7 @@ export class CollectionDockingView extends CollectionSubView() {
_freeform_backgroundGrid: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
- this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+ inheritParentAcls(this.dataDoc, docToAdd, false);
CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
}
})
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 64f9c6a87..06522b85e 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -7,8 +7,7 @@ import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, numberRange, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { CompileScript } from '../../util/Scripting';
@@ -98,14 +97,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
this._createEmbeddingSelected = false;
if (de.complete.docDragData) {
- this.props.parent.Document.dropConverter instanceof ScriptField && this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
const key = this.props.pivotField;
const castedValue = this.getValue(this.heading);
const onLayoutDoc = this.onLayoutDoc(key);
- de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
- this.props.parent.onInternalDrop(e, de);
- e.stopPropagation();
+ if (this.props.parent.onInternalDrop(e, de)) {
+ de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
+ }
+ return true;
}
+ return false;
});
getValue = (value: string): any => {
@@ -251,10 +251,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
return (
<div className="collectionStackingView-optionPicker">
<div className="optionOptions">
- <div className={'optionPicker' + (selected === true ? ' active' : '')} onClick={this.toggleEmbedding}>
- Create Embedding
- </div>
- <div className={'optionPicker' + (selected === true ? ' active' : '')} onClick={this.deleteRow}>
+ <div className={'optionPicker' + (selected === true ? ' active' : '')} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
Delete
</div>
</div>
@@ -272,13 +269,16 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
return this.collapsed ? null : (
<div style={{ position: 'relative' }}>
+ {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null}
{showChrome ? (
<div
className="collectionStackingView-addDocumentButton"
- style={{
- //width: style.columnWidth / style.numGroupColumns,
- padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`,
- }}>
+ style={
+ {
+ //width: style.columnWidth / style.numGroupColumns,
+ //padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`,
+ }
+ }>
<EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} />
</div>
) : null}
@@ -287,12 +287,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
ref={this._contRef}
style={{
padding: stackPad,
+ minHeight: this.props.showHandle && this.props.parent.props.isContentActive() ? '10px' : undefined,
width: this.props.parent.NodeWidth,
gridGap: this.props.parent.gridGap,
gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ''),
}}>
{this.props.parent.children(this.props.docList)}
- {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null}
</div>
</div>
);
@@ -312,23 +312,33 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
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 : 'lightgrey' }}>
- {noChrome ? evContents : editableHeaderView}
+ {noChrome ? evContents : <div>{editableHeaderView}</div>}
{noChrome || 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"
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ action(e => (this._paletteOn = !this._paletteOn))
+ )
+ }>
<FontAwesomeIcon icon="palette" size="lg" />
</button>
{this._paletteOn ? this.renderColorPicker() : null}
</div>
)}
{noChrome ? null : (
- <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}>
+ <button className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}>
<FontAwesomeIcon icon={this.collapsed ? 'chevron-down' : 'chevron-up'} size="lg" />
</button>
)}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
- <div className="collectionStackingView-sectionOptions">
- <Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderMenu()}>
+ <div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}>
+ <Flyout anchorPoint={anchorPoints.RIGHT_TOP} content={this.renderMenu()}>
<button className="collectionStackingView-sectionOptionButton">
<FontAwesomeIcon icon="ellipsis-v" size="lg" />
</button>
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index c35f088a6..6eeccc94e 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -8,652 +8,15 @@
background-color: $dark-gray;
height: 35px;
border-bottom: $standard-border;
- padding-right: 5px;
+ padding: 0 10px;
align-items: center;
+ overflow-x: scroll;
+ &::-webkit-scrollbar {
+ display: none;
+ }
- .collectionMenu-hardCodedButton {
- cursor: pointer;
- color: $white;
- width: 25px;
- height: 25px;
- padding: 5;
- text-align: center;
+ .hardCodedButtons {
display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
- transition: 0.2s;
- border-radius: 3px;
-
- &:hover {
- background-color: rgba(0, 0, 0, 0.2);
- }
+ flex-direction: row;
}
-}
-
-// .collectionMenu-cont {
-// position: relative;
-// display: inline-flex;
-// width: 100%;
-// opacity: 0.9;
-// z-index: 901;
-// transition: top .5s;
-// background: $dark-gray;
-// color: $white;
-// transform-origin: top left;
-// top: 0;
-// width: 100%;
-
-// .recordButtonOutline {
-// border-radius: 100%;
-// width: 18px;
-// height: 18px;
-// border: solid 1px $white;
-// display: flex;
-// align-items: center;
-// justify-content: center;
-// }
-
-// .recordButtonInner {
-// border-radius: 100%;
-// width: 70%;
-// height: 70%;
-// background: $white;
-// }
-
-// .collectionMenu {
-// display: flex;
-// height: 100%;
-// overflow: visible;
-// z-index: 901;
-// border: unset;
-
-// .collectionMenu-divider {
-// height: 100%;
-// margin-left: 3px;
-// margin-right: 3px;
-// width: 2px;
-// background-color: $medium-gray;
-// }
-
-// .collectionViewBaseChrome {
-// display: flex;
-// align-items: center;
-
-// .collectionViewBaseChrome-viewPicker {
-// font-size: $small-text;
-// outline-color: $black;
-// color: $white;
-// border: none;
-// background: $dark-gray;
-// }
-
-// .collectionViewBaseChrome-viewPicker:focus {
-// outline: none;
-// border: none;
-// }
-
-// .collectionViewBaseChrome-viewPicker:active {
-// outline-color: $black;
-// }
-
-// .collectionViewBaseChrome-button {
-// font-size: $small-text;
-// text-transform: uppercase;
-// letter-spacing: 2px;
-// background: $white;
-// color: $pink;
-// outline-color: $black;
-// border: none;
-// padding: 12px 10px 11px 10px;
-// margin-left: 10px;
-// }
-
-// .collectionViewBaseChrome-cmdPicker {
-// margin-left: 3px;
-// margin-right: 0px;
-// font-size: $small-text;
-// text-transform: capitalize;
-// color: $white;
-// border: none;
-// background: $dark-gray;
-// }
-
-// .collectionViewBaseChrome-cmdPicker:focus {
-// border: none;
-// outline: none;
-// }
-
-// .commandEntry-outerDiv {
-// pointer-events: all;
-// background-color: transparent;
-// display: flex;
-// flex-direction: row;
-// align-items: center;
-// justify-content: center;
-// height: 100%;
-// overflow: hidden;
-
-// .commandEntry-drop {
-// color: $white;
-// width: 30px;
-// margin-top: auto;
-// margin-bottom: auto;
-// }
-// }
-
-// .commandEntry-outerDiv:hover{
-// background-color: $drop-shadow;
-
-// .collectionViewBaseChrome-viewPicker,
-// .collectionViewBaseChrome-cmdPicker{
-// background: $dark-gray;
-// }
-// }
-
-// .collectionViewBaseChrome-collapse {
-// transition: all .5s, opacity 0.3s;
-// position: absolute;
-// width: 30px;
-// transform-origin: top left;
-// pointer-events: all;
-// // margin-top: 10px;
-// }
-
-// @media only screen and (max-device-width: 480px) {
-// .collectionViewBaseChrome-collapse {
-// display: none;
-// }
-// }
-
-// .collectionViewBaseChrome-template,
-// .collectionViewBaseChrome-viewModes {
-// align-items: center;
-// height: 100%;
-// display: flex;
-// background: transparent;
-// color: $medium-gray;
-// justify-content: center;
-// }
-
-// .collectionViewBaseChrome-viewSpecs {
-// margin-left: 5px;
-// display: grid;
-// border: none;
-// border-right: solid $medium-gray 1px;
-
-// .collectionViewBaseChrome-filterIcon {
-// position: relative;
-// display: flex;
-// margin: auto;
-// background: $dark-gray;
-// color: $white;
-// width: 30px;
-// height: 30px;
-// align-items: center;
-// justify-content: center;
-// border: none;
-// border-right: solid $medium-gray 1px;
-// }
-
-// .collectionViewBaseChrome-viewSpecsInput {
-// padding: 12px 10px 11px 10px;
-// border: 0px;
-// color: $medium-gray;
-// text-align: center;
-// letter-spacing: 2px;
-// outline-color: $black;
-// font-size: $small-text;
-// background: $white;
-// height: 100%;
-// width: 75px;
-// }
-
-// .collectionViewBaseChrome-viewSpecsMenu {
-// overflow: hidden;
-// transition: height .5s, display .5s;
-// position: absolute;
-// top: 60px;
-// z-index: 100;
-// display: flex;
-// flex-direction: column;
-// background: $white;
-// box-shadow: $medium-gray 2px 2px 4px;
-
-// .qs-datepicker {
-// left: unset;
-// right: 0;
-// }
-
-// .collectionViewBaseChrome-viewSpecsMenu-row {
-// display: grid;
-// grid-template-columns: 150px 200px 150px;
-// margin-top: 10px;
-// margin-right: 10px;
-
-// .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
-// .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
-// .collectionViewBaseChrome-viewSpecsMenu-rowRight {
-// font-size: $small-text;
-// letter-spacing: 2px;
-// color: $medium-gray;
-// margin-left: 10px;
-// padding: 5px;
-// border: none;
-// outline-color: $black;
-// }
-// }
-
-// .collectionViewBaseChrome-viewSpecsMenu-lastRow {
-// display: grid;
-// grid-template-columns: 1fr 1fr 1fr;
-// grid-gap: 10px;
-// margin: 10px;
-// }
-// }
-// }
-// }
-
-// .collectionStackingViewChrome-cont,
-// .collectionTreeViewChrome-cont,
-// .collection3DCarouselViewChrome-cont {
-// display: flex;
-// justify-content: space-between;
-// }
-
-// .collectionGridViewChrome-cont {
-// display: flex;
-// margin-left: 10;
-
-// .collectionGridViewChrome-viewPicker {
-// font-size: $small-text;
-// //text-transform: uppercase;
-// //letter-spacing: 2px;
-// background: $dark-gray;
-// color: $white;
-// outline-color: $black;
-// color: $white;
-// border: none;
-// border-right: solid $medium-gray 1px;
-// }
-
-// .collectionGridViewChrome-viewPicker:active {
-// outline-color: $black;
-// }
-
-// .grid-control {
-// align-self: center;
-// display: flex;
-// flex-direction: row;
-// margin-right: 5px;
-
-// .grid-icon {
-// margin-right: 5px;
-// align-self: center;
-// }
-
-// .flexLabel {
-// margin-bottom: 0;
-// }
-
-// .collectionGridViewChrome-entryBox {
-// width: 50%;
-// color: $black;
-// }
-
-// .collectionGridViewChrome-columnButton {
-// color: $black;
-// }
-// }
-// }
-
-// .collectionStackingViewChrome-sort,
-// .collectionTreeViewChrome-sort {
-// display: flex;
-// align-items: center;
-// justify-content: space-between;
-
-// .collectionStackingViewChrome-sortIcon,
-// .collectionTreeViewChrome-sortIcon {
-// transition: transform .5s;
-// margin-left: 10px;
-// }
-// }
-
-// button:hover {
-// transform: scale(1);
-// }
-
-
-// .collectionStackingViewChrome-pivotField-cont,
-// .collectionTreeViewChrome-pivotField-cont,
-// .collection3DCarouselViewChrome-scrollSpeed-cont {
-// justify-self: right;
-// align-items: center;
-// display: flex;
-// grid-auto-columns: auto;
-// font-size: $small-text;
-// letter-spacing: 2px;
-
-// .collectionStackingViewChrome-pivotField-label,
-// .collectionTreeViewChrome-pivotField-label,
-// .collection3DCarouselViewChrome-scrollSpeed-label {
-// grid-column: 1;
-// margin-right: 7px;
-// user-select: none;
-// font-family: $sans-serif;
-// letter-spacing: normal;
-// }
-
-// .collectionStackingViewChrome-sortIcon {
-// transition: transform .5s;
-// grid-column: 3;
-// text-align: center;
-// display: flex;
-// justify-content: center;
-// align-items: center;
-// cursor: pointer;
-// width: 25px;
-// height: 25px;
-// border-radius: 100%;
-// }
-
-// .collectionStackingViewChrome-sortIcon:hover {
-// background-color: $drop-shadow;
-// }
-
-// .collectionStackingViewChrome-pivotField,
-// .collectionTreeViewChrome-pivotField,
-// .collection3DCarouselViewChrome-scrollSpeed {
-// color: $white;
-// grid-column: 2;
-// grid-row: 1;
-// width: 90%;
-// min-width: 100px;
-// display: flex;
-// height: 80%;
-// border-radius: 7px;
-// align-items: center;
-// background: $white;
-
-// .editable-view-input,
-// input,
-// .editableView-container-editing-oneLine,
-// .editableView-container-editing {
-// margin: auto;
-// border: 0px;
-// color: $light-gray !important;
-// text-align: center;
-// letter-spacing: 2px;
-// outline-color: $black;
-// height: 100%;
-// }
-
-// .react-autosuggest__container {
-// margin: 0;
-// color: $medium-gray;
-// padding: 0px;
-// }
-// }
-// }
-
-// .collectionStackingViewChrome-pivotField:hover,
-// .collectionTreeViewChrome-pivotField:hover,
-// .collection3DCarouselViewChrome-scrollSpeed:hover {
-// cursor: text;
-// }
-
-// }
-// }
-
-// .collectionMenu-webUrlButtons {
-// margin-left: 44;
-// background: lightGray;
-// display: flex;
-// }
-
-// .webBox-urlEditor {
-// position: relative;
-// opacity: 0.9;
-// z-index: 901;
-// transition: top .5s;
-
-// .urlEditor {
-// display: grid;
-// grid-template-columns: 1fr auto;
-// padding-bottom: 10px;
-// overflow: hidden;
-// margin-top: 5px;
-// height: 35px;
-
-// .editorBase {
-// display: flex;
-
-// .editor-collapse {
-// transition: all .5s, opacity 0.3s;
-// position: absolute;
-// width: 40px;
-// transform-origin: top left;
-// }
-
-// .switchToText {
-// color: $medium-gray;
-// }
-
-// .switchToText:hover {
-// color: $dark-gray;
-// }
-// }
-
-// button:hover {
-// transform: scale(1);
-// }
-// }
-// }
-
-// .collectionMenu-urlInput {
-// padding: 12px 10px 11px 10px;
-// border: 0px;
-// color: $black;
-// font-size: $small-text;
-// letter-spacing: 2px;
-// outline-color: $black;
-// background: $white;
-// width: 100%;
-// min-width: 350px;
-// margin-right: 10px;
-// height: 100%;
-// }
-
-// .collectionFreeFormMenu-cont {
-// display: inline-flex;
-// position: relative;
-// align-items: center;
-// height: 100%;
-
-// .color-previewI {
-// width: 60%;
-// top: 80%;
-// position: absolute;
-// height: 4px;
-// }
-
-// .color-previewII {
-// width: 80%;
-// height: 80%;
-// margin-left: 10%;
-// position: absolute;
-// bottom: 5;
-// }
-
-// .btn-group {
-// display: grid;
-// grid-template-columns: auto auto auto auto;
-// margin: auto;
-// /* Make the buttons appear below each other */
-// }
-
-// .btn-draw {
-// display: inline-flex;
-// margin: auto;
-// /* Make the buttons appear below each other */
-// }
-
-// .fwdKeyframe,
-// .numKeyframe,
-// .backKeyframe {
-// cursor: pointer;
-// position: relative;
-// width: 20;
-// height: 30;
-// bottom: 0;
-// background: $dark-gray;
-// display: inline-flex;
-// align-items: center;
-// color: $white;
-// }
-
-// .backKeyframe {
-// svg {
-// display: block;
-// margin: auto;
-// }
-// }
-
-
-// .numKeyframe {
-// flex-direction: column;
-// padding-top: 5px;
-// }
-
-// .fwdKeyframe {
-// svg {
-// display: block;
-// margin: auto;
-// }
-
-// border-right: solid $medium-gray 1px;
-// }
-// }
-
-// .collectionSchemaViewChrome-cont {
-// display: flex;
-// font-size: $small-text;
-
-// .collectionSchemaViewChrome-toggle {
-// display: flex;
-// margin-left: 10px;
-// }
-
-// .collectionSchemaViewChrome-label {
-// text-transform: uppercase;
-// letter-spacing: 2px;
-// margin-right: 5px;
-// display: flex;
-// flex-direction: column;
-// justify-content: center;
-// }
-
-// .collectionSchemaViewChrome-toggler {
-// width: 100px;
-// height: 35px;
-// background-color: $black;
-// position: relative;
-// }
-
-// .collectionSchemaViewChrome-togglerButton {
-// width: 47px;
-// height: 30px;
-// background-color: $light-gray;
-// // position: absolute;
-// transition: all 0.5s ease;
-// // top: 3px;
-// margin-top: 3px;
-// color: $medium-gray;
-// letter-spacing: 2px;
-// text-transform: uppercase;
-// display: flex;
-// flex-direction: column;
-// justify-content: center;
-// text-align: center;
-
-// &.on {
-// margin-left: 3px;
-// }
-
-// &.off {
-// margin-left: 50px;
-// }
-// }
-// }
-
-
-// .commandEntry-outerDiv {
-// display: flex;
-// flex-direction: column;
-// height: 40px;
-// }
-
-// .commandEntry-inputArea {
-// display: flex;
-// flex-direction: row;
-// width: 150px;
-// margin: auto auto auto auto;
-// }
-
-// .react-autosuggest__container {
-// position: relative;
-// width: 100%;
-// margin-left: 5px;
-// margin-right: 5px;
-// }
-
-// .react-autosuggest__input {
-// border: 1px solid $light-gray;
-// border-radius: 4px;
-// width: 100%;
-// }
-
-// .react-autosuggest__input--focused {
-// outline: none;
-// }
-
-// .react-autosuggest__input--open {
-// border-bottom-left-radius: 0;
-// border-bottom-right-radius: 0;
-// }
-
-// .react-autosuggest__suggestions-container {
-// display: none;
-// }
-
-// .react-autosuggest__suggestions-container--open {
-// display: block;
-// position: fixed;
-// overflow-y: auto;
-// max-height: 400px;
-// width: 180px;
-// border: 1px solid $light-gray;
-// background-color: $white;
-// font-family: $sans-serif;
-// font-weight: 300;
-// font-size: $large-header;
-// border-bottom-left-radius: 4px;
-// border-bottom-right-radius: 4px;
-// z-index: 2;
-// }
-
-// .react-autosuggest__suggestions-list {
-// margin: 0;
-// padding: 0;
-// list-style-type: none;
-// }
-
-// .react-autosuggest__suggestion {
-// cursor: pointer;
-// padding: 10px 20px;
-// }
-
-// .react-autosuggest__suggestion--highlighted {
-// background-color: $light-gray;
-// } \ No newline at end of file
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index fc3f713ce..f65e8698f 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -2,23 +2,23 @@ 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 } from 'mobx';
+import { Toggle, ToggleType, Type } from 'browndash-components';
+import { Lambda, action, computed, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState } from 'react-color';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
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, DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { Document } from '../../../fields/documentSchemas';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
@@ -28,20 +28,19 @@ 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 { MainView } from '../MainView';
+import { DefaultStyleProvider } from '../StyleProvider';
import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocumentView, OpenWhereMod } from '../nodes/DocumentView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { DocumentView, DocumentViewInternal, OpenWhereMod } from '../nodes/DocumentView';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-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';
import { CollectionFreeFormView } from './collectionFreeForm';
+import { CollectionLinearView } from './collectionLinear';
interface CollectionMenuProps {
panelHeight: () => number;
@@ -95,6 +94,15 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
};
+ @action
+ toggleProperties = () => {
+ if (MainView.Instance.propertiesWidth() > 0) {
+ SettingsManager.propertiesWidth = 0;
+ } else {
+ SettingsManager.propertiesWidth = 300;
+ }
+ };
+
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
@@ -121,7 +129,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
docViewPath={returnEmptyDoclist}
moveDocument={returnFalse}
addDocument={returnFalse}
- addDocTab={returnFalse}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
removeDocument={returnFalse}
ScreenToLocalTransform={this.buttonBarXf}
@@ -139,23 +147,45 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
}
render() {
- const propIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
- const propTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
-
- 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>
+ const headerIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
+ const headerTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
+ const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+ const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
+
+ const hardCodedButtons = (
+ <div className={`hardCodedButtons`}>
+ <Toggle
+ toggleType={ToggleType.BUTTON}
+ type={Type.PRIM}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={this.toggleTopBar}
+ toggleStatus={SettingsManager.headerBarHeight > 0}
+ icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
+ tooltip={headerTitle}
+ />
+ <Toggle
+ toggleType={ToggleType.BUTTON}
+ type={Type.PRIM}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={this.toggleProperties}
+ toggleStatus={SettingsManager.propertiesWidth > 0}
+ icon={<FontAwesomeIcon icon={propIcon} size="lg" />}
+ tooltip={propTitle}
+ />
+ </div>
);
// NEW BUTTONS
//dash col linear view buttons
const contMenuButtons = (
- <div className="collectionMenu-container">
+ <div
+ className="collectionMenu-container"
+ style={{
+ background: SettingsManager.Instance.userBackgroundColor,
+ // borderColor: StrCast(Doc.UserDoc().userColor)
+ }}>
{this.contMenuButtons}
- {prop}
+ {hardCodedButtons}
</div>
);
@@ -427,8 +457,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
if (docDragData?.draggedDocuments.length) {
this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
e.stopPropagation();
+ return true;
}
- return true;
+ return false;
}
dragViewDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 675f23970..53a42d2a6 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -10,7 +10,7 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -47,7 +47,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
@observable _cursor: CursorProperty = 'grab';
@observable _scroll = 0;
@computed get chromeHidden() {
- return BoolCast(this.layoutDoc.chromeHidden);
+ return BoolCast(this.layoutDoc.chromeHidden) || this.props.onBrowseClick?.() ? true : false;
}
// columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
@computed get colHeaderData() {
@@ -192,7 +192,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
if (found) {
const top = found.getBoundingClientRect().top;
const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
- if (Math.floor(localTop[1]) !== 0) {
+ if (Math.floor(localTop[1]) !== 0 && Math.ceil(this.props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) {
let focusSpeed = options.zoomTime ?? 500;
smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
return focusSpeed;
@@ -250,8 +250,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
rootSelected={this.rootSelected}
layout_showTitle={this.props.childlayout_showTitle}
- dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+ dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
+ onBrowseClick={this.props.onBrowseClick}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={noteTakingDocTransform}
focus={this.focusDocument}
@@ -441,23 +442,24 @@ export class CollectionNoteTakingView extends CollectionSubView() {
docs.splice(previousDocIndex + 1, 0, ...newDocs);
}
}
+ return true;
}
} else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
- this.props.addDocument?.(source);
+ if (!this.props.addDocument?.(source)) e.preventDefault();
de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: '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);
+ return true;
+ } 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) {
const dropCreator = annoDragData.dropDocCreator;
- annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => {
- const dropDoc = dropCreator(annotationOn);
- return dropDoc || this.rootDoc;
- };
+ annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => dropCreator(annotationOn) || this.rootDoc;
return true;
}
@@ -619,7 +621,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
@computed get backgroundEvents() {
- return SnappingManager.GetIsDragging();
+ return this.props.isContentActive() === false ? 'none' : undefined;
}
observer: any;
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 2f28ecd00..3286d60bd 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -92,6 +92,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+ return true;
});
getValue = (value: string): any => {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 57d9bbb49..bbd528e13 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -52,20 +52,25 @@ export class CollectionPileView extends CollectionSubView() {
@computed get toggleIcon() {
return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
}
+ @computed get contentEvents() {
+ const isStarburst = this.layoutEngine() === computeStarburstLayout.name;
+ return this.props.isContentActive() && isStarburst ? undefined : 'none';
+ }
// returns the contents of the pileup in a CollectionFreeFormView
@computed get contents() {
- const isStarburst = this.layoutEngine() === computeStarburstLayout.name;
return (
- <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}>
+ <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
<CollectionFreeFormView
- {...this.props}
- childContentsActive={returnFalse}
+ {...this.props} //
layoutEngine={this.layoutEngine}
addDocument={this.addPileDoc}
- childCanEmbedOnDrag={true}
- childClickScript={this.toggleIcon}
moveDocument={this.removePileDoc}
+ // pile children never have their contents active, but will be document active whenever the entire pile is.
+ childContentsActive={returnFalse}
+ childDocumentsActive={this.props.isDocumentActive}
+ childDragAction="move"
+ childClickScript={this.toggleIcon}
/>
</div>
);
@@ -88,13 +93,14 @@ export class CollectionPileView extends CollectionSubView() {
this.layoutDoc._freeform_panY = -10;
this.props.Document._freeform_pileEngine = computePassLayout.name;
} else {
- const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500);
+ const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400);
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2;
this.layoutDoc._freeform_pileWidth = this.layoutDoc[Width]();
this.layoutDoc._freeform_pileHeight = this.layoutDoc[Height]();
this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
this.layoutDoc._width = this.layoutDoc._height = defaultSize;
+ this.layoutDoc.background;
this.props.Document._freeform_pileEngine = computeStarburstLayout.name;
}
});
@@ -103,7 +109,6 @@ export class CollectionPileView extends CollectionSubView() {
_undoBatch: UndoManager.Batch | undefined;
pointerDown = (e: React.PointerEvent) => {
let dist = 0;
- SnappingManager.SetIsDragging(true);
setupMoveUpEvents(
this,
e,
@@ -126,7 +131,6 @@ export class CollectionPileView extends CollectionSubView() {
() => {
this._undoBatch?.end();
this._undoBatch = undefined;
- SnappingManager.SetIsDragging(false);
},
emptyFunction,
e.shiftKey && this.layoutEngine() === computePassLayout.name,
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index a55b70e22..a19d8e696 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -104,7 +104,7 @@
.collectionStackedTimeline-left-resizer,
.collectionStackedTimeline-resizer {
- background: $medium-gray;
+ background: $dark-gray;
position: absolute;
top: 0;
height: 100%;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 9d5cb257a..de58e1fe7 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -119,8 +119,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// onClick play scripts
CollectionStackedTimeline.RangeScript =
CollectionStackedTimeline.RangeScript ||
- ScriptField.MakeFunction(`setTimeout(() => scriptContext.clickAnchor(this, clientX))`, {
- // setTimeout is a hack to run script in its own properly named undo group (instead of being part of the generic onClick)
+ ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
self: Doc.name,
scriptContext: 'any',
clientX: 'number',
@@ -370,22 +369,22 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// handles dragging and dropping markers in timeline
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
- 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);
- });
+ if (super.onInternalDrop(e, de)) {
+ // 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;
+ return true;
+ }
+ return false;
}
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
@@ -406,7 +405,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any,
_label_minFontSize: 12,
_label_maxFontSize: 24,
- _stayInCollection: true,
+ _dragOnlyWithinContainer: true,
backgroundColor: 'rgba(128, 128, 128, 0.5)',
layout_hideLinkButton: true,
onClick: FollowLinkScript(),
@@ -528,6 +527,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
);
}
+ @computed get timelineEvents() {
+ return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined;
+ }
render() {
const overlaps: {
anchorStartTime: number;
@@ -540,7 +542,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
return this.clipDuration === 0 ? null : (
- <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}>
+ <div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}>
<div
className="collectionStackedTimeline-timelineContainer"
style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
@@ -714,7 +716,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
this._disposer = reaction(
() => this.props.currentTimecode(),
time => {
- const dictationDoc = Cast(this.props.layoutDoc['data-dictation'], Doc, null);
+ const dictationDoc = Cast(this.props.layoutDoc.data_dictation, Doc, null);
const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
if (
!LightboxView.LightboxDoc &&
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 99a68e94b..255bc3889 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -128,6 +128,7 @@
height: 15;
position: absolute;
margin-left: -5;
+ z-index: 10;
}
// Documents in stacking view
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 3c0d8cbc3..e4a0d6dad 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,7 +1,7 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData, Height, Width } from '../../../fields/DocSymbols';
@@ -11,13 +11,13 @@ 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, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
@@ -58,7 +58,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// 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 = 'ew-resize';
// 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?
@@ -84,7 +84,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
}
@computed get xMargin() {
- return NumCast(this.layoutDoc._xMargin, Math.min(3, 0.05 * this.props.PanelWidth()));
+ return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this.props.PanelWidth()));
}
@computed get yMargin() {
return this.props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this.props.PanelWidth()));
@@ -295,7 +295,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return this.addDocument?.(newDoc);
}
};
- isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
+ isContentActive = () => (this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
@observable _renderCount = 5;
isChildContentActive = () =>
@@ -307,19 +307,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
? false
: undefined;
isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined);
+ @observable docRefs = new ObservableMap<Doc, DocumentView>();
// 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, count: number) {
const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
- let dref: Opt<DocumentView>;
- const stackedDocTransform = () => this.getDocTransform(doc, dref);
+ const stackedDocTransform = () => this.getDocTransform(doc);
this._docXfs.push({ stackedDocTransform, width, height });
- //DocumentView is how the node will be rendered
return count > this._renderCount ? null : (
<DocumentView
- ref={r => (dref = r || undefined)}
+ ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))}
Document={doc}
DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
renderDepth={this.props.renderDepth + 1}
@@ -341,7 +340,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
rootSelected={this.rootSelected}
layout_showTitle={this.props.childlayout_showTitle}
- dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+ dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={stackedDocTransform}
@@ -367,9 +366,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
);
}
- 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);
+ getDocTransform(doc: Doc) {
+ const dref = this.docRefs.get(doc);
+ this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
+ const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
// 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);
}
@@ -402,11 +402,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
// This following three functions must be from the view Mehek showed
columnDividerDown = (e: React.PointerEvent) => {
runInAction(() => (this._cursor = 'grabbing'));
+ const batch = UndoManager.StartBatch('stacking width');
setupMoveUpEvents(
this,
e,
this.onDividerMove,
- action(() => (this._cursor = 'grab')),
+ action(() => {
+ this._cursor = 'ew-resize';
+ batch.end();
+ }),
emptyFunction
);
};
@@ -418,7 +422,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@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` }}>
+ <div
+ className="collectionStackingView-columnDragger"
+ onPointerDown={this.columnDividerDown}
+ ref={this._draggerRef}
+ style={{ cursor: this._cursor, color: StrCast(Doc.UserDoc().userColor), left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}>
<FontAwesomeIcon icon={'arrows-alt-h'} />
</div>
);
@@ -455,18 +463,24 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const docs = this.childDocList;
// still figuring out where to add the document
if (docs && newDocs.length) {
+ newDocs.forEach(newdoc => docs.indexOf(newdoc) !== -1 && docs.splice(docs.indexOf(newdoc), 1));
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);
newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1));
docs.splice(insertInd - offset, 0, ...newDocs);
}
+ return true;
}
} else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
- this.props.addDocument?.(source);
+ if (!this.props.addDocument?.(source)) e.preventDefault();
de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: '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);
+ return true;
+ } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) {
+ return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
+ }
+ e.preventDefault();
return false;
};
@@ -584,7 +598,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
action((entries: any) => {
if (this.layoutDoc._layout_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.props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
}
})
);
@@ -655,7 +669,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (menuDoc) {
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
@@ -700,7 +713,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
@computed get backgroundEvents() {
- return SnappingManager.GetIsDragging();
+ return this.props.isContentActive() === false ? 'none' : undefined;
}
observer: any;
render() {
@@ -710,7 +723,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
SetValue: this.addGroup,
contents: '+ ADD A GROUP',
};
- const buttonMenu = this.rootDoc.buttonMenu;
+ const buttonMenu = this.rootDoc.layout_headerButton;
const noviceExplainer = this.rootDoc.layout_explainer;
return (
<>
@@ -727,7 +740,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
style={{
overflowY: this.isContentActive() ? 'auto' : 'hidden',
background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: (this.props.pointerEvents?.() as any) ?? (this.backgroundEvents ? 'all' : undefined),
+ pointerEvents: (this.props.pointerEvents?.() as any) ?? this.backgroundEvents,
}}
onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
onDrop={this.onExternalDrop.bind(this)}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 6be9cb72d..ebb4ba5a1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -94,6 +94,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+ return true;
});
getValue = (value: string): any => {
const parsed = parseInt(value);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d1b7f6ff6..c189ef126 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -185,12 +185,14 @@ export function CollectionSubView<X>(moreProps?: X) {
@undoBatch
protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
- protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) {
+ protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) {
if (de.complete.docDragData) {
- // if targetDropAction is, say 'embed', but we're just dragging within a collection, we want to ignore the targetAction.
- // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys)
- if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
- de.complete.docDragData.dropAction = targetAction;
+ // override the dropEvent's dropAction
+ const dropAction = this.layoutDoc.dropAction as dropActionType;
+ // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
+ // so we check if our collection has a dropAction set on it and if so, we use that instead.
+ if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
+ de.complete.docDragData.dropAction = dropAction;
}
e.stopPropagation();
}
@@ -203,7 +205,7 @@ export function CollectionSubView<X>(moreProps?: X) {
protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
if (docDragData) {
- let added = false;
+ let added = undefined;
const dropAction = docDragData.dropAction || docDragData.userDropAction;
const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
@@ -212,21 +214,21 @@ export function CollectionSubView<X>(moreProps?: X) {
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._type_collection === CollectionViewType.Pile || de.embedKey || 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);
+ const canAdd = de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc);
+ const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse);
+ added = canAdd || moved ? moved : undefined;
} else {
- ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
+ ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
added = addedDocs.length ? this.addDocument(addedDocs) : true;
}
- added && e.stopPropagation();
- return added;
} else {
- ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
+ ScriptCast(this.rootDoc.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;
+ added === false && !this.props.isAnnotationOverlay && e.preventDefault();
+ added === true && e.stopPropagation();
+ return added ? true : false;
} else if (de.complete.annoDragData) {
const dropCreator = de.complete.annoDragData.dropDocCreator;
de.complete.annoDragData.dropDocCreator = () => {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 60e6815e5..a8f5345b7 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -34,8 +34,6 @@ export class CollectionTimeView extends CollectionSubView() {
async componentDidMount() {
this.props.setContentView?.(this);
- //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
- ///const childText = "const embedding = getEmbedding(self); switchView(embedding, detailView); embedding.dropAction='embed'; useRightSplit(embedding, shiftKey); ";
runInAction(() => {
this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name });
this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
@@ -67,9 +65,9 @@ export class CollectionTimeView extends CollectionSubView() {
@action
scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
// if in preview, then override document's fields with view spec
- this._focusFilters = StrListCast(anchor.presDocFilters);
- this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
- this._focusPivotField = StrCast(anchor.presPivotField);
+ this._focusFilters = StrListCast(anchor.config_docFilters);
+ this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters);
+ this._focusPivotField = StrCast(anchor.config_pivotField);
return undefined;
};
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 273b08247..2bf649caf 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -13,7 +13,7 @@
width: 100%;
position: relative;
top: 0;
- background: $light-gray;
+ // background: $light-gray;
font-size: 13px;
overflow: auto;
user-select: none;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ed1e0c067..eed04b3ee 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
import { DocData, Height, Width } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
-import { InkTool } from '../../../fields/InkField';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -67,17 +66,17 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return this.props.DataDoc || this.doc;
}
@computed get treeViewtruncateTitleWidth() {
- return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth());
+ return NumCast(this.doc.treeView_TruncateTitleWidth, this.panelWidth());
}
@computed get treeChildren() {
TraceMobx();
return this.props.childDocuments || this.childDocs;
}
@computed get outlineMode() {
- return this.doc.treeViewType === TreeViewType.outline;
+ return this.doc.treeView_Type === TreeViewType.outline;
}
@computed get fileSysMode() {
- return this.doc.treeViewType === TreeViewType.fileSystem;
+ return this.doc.treeView_Type === TreeViewType.fileSystem;
}
@computed get dashboardMode() {
return this.doc === Doc.MyDashboards;
@@ -90,8 +89,7 @@ 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) =>
- Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) ? true : false;
+ isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this.props.isContentActive() ? true : false);
componentWillUnmount() {
this._isDisposing = true;
@@ -142,11 +140,17 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
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) => {
+ protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => {
+ const dropAction = this.layoutDoc.dropAction as dropActionType;
const dragData = de.complete.docDragData;
if (dragData) {
- const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
- dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? 'same' : dragData.dropAction;
+ const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.rootDoc) ? true : false;
+ const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
+ if (isAlreadyInTree() !== sameTree) {
+ console.log('WHAAAT');
+ }
+ dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree ? 'same' : dragData.dropAction;
+ e.stopPropagation();
}
};
@@ -177,7 +181,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const doAddDoc = (doc: Doc | Doc[]) =>
(doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before);
- res && (doc.embedContainer = this.props.Document);
+ res && Doc.SetContainer(doc, this.props.Document);
return res;
}, true);
if (this.doc.resolvedDataDoc instanceof Promise) return false;
@@ -187,9 +191,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
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' });
+ layoutItems.push({ description: 'Make tree state ' + (this.doc.treeView_OpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeView_OpenIsTransient = !this.doc.treeView_OpenIsTransient), icon: 'paint-brush' });
+ layoutItems.push({ description: (this.doc.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeView_HideHeaderFields = !this.doc.treeView_HideHeaderFields), icon: 'paint-brush' });
+ layoutItems.push({ description: (this.doc.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeView_HideTitle = !this.doc.treeView_HideTitle), 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 : [];
@@ -211,7 +215,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
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);
+ if (enter && this.props.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
this.dataDoc.title = value;
return true;
})}
@@ -257,11 +261,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const icons = StrListCast(this.doc.childContextMenuIcons);
return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
};
- headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields);
+ headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeView_HideHeaderFields);
@observable _renderCount = 1;
@computed get treeViewElements() {
TraceMobx();
- const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
+ const dragAction = StrCast(this.doc.childDragAction) as dropActionType;
const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
@@ -276,7 +280,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
addDoc,
this.remove,
moveDoc,
- dropAction,
+ dragAction,
this.props.addDocTab,
this.props.styleProvider,
this.screenToLocalTransform,
@@ -383,8 +387,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@observable _headerHeight = 0;
@computed get content() {
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 color = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.Color);
+ const pointerEvents = () => (this.props.isContentActive() === false ? 'none' : undefined);
+ const titleBar = this.props.treeViewHideTitle || this.doc.treeView_HideTitle ? null : this.titleBar;
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
{!this.buttonMenu && !this.noviceExplainer ? null : (
@@ -396,8 +401,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<div
className="collectionTreeView-contents"
key="tree"
+ ref={r => !this.doc.treeView_HasOverlay && r && this.createTreeDropTarget(r)}
style={{
...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
+ color: color(),
overflow: 'auto',
width: '100%',
height: '100%',
@@ -419,8 +426,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
minHeight: '100%',
}}
onWheel={e => e.stopPropagation()}
- onDrop={this.onTreeDrop}
- ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}>
+ onClick={e => (!this.layoutDoc.forceActive ? this.props.select(false) : SelectionManager.DeselectAll())}
+ onDrop={this.onTreeDrop}>
<ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
</div>
</div>
@@ -434,18 +441,18 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1) || 1;
return (
<div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
- {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? (
+ {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeView_HasOverlay ? (
<CollectionFreeFormView
{...this.props}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
+ pointerEvents={this.props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone}
isAnnotationOverlay={true}
isAnnotationOverlayScrollable={true}
childDocumentsActive={this.props.isDocumentActive}
fieldKey={this.props.fieldKey + '_annotations'}
- dropAction={'move'}
+ dropAction="move"
select={emptyFunction}
addDocument={this.addAnnotationDocument}
removeDocument={this.remAnnotationDocument}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7913d3188..88f892efc 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -9,6 +9,7 @@ import { TraceMobx } from '../../../fields/util';
import { returnEmptyString } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
+import { dropActionType } from '../../util/DragManager';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { InteractionUtils } from '../../util/InteractionUtils';
import { ContextMenu } from '../ContextMenu';
@@ -40,7 +41,7 @@ interface CollectionViewProps_ extends FieldViewProps {
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;
- setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => void;
+ setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => void;
ignoreUnrendered?: boolean;
// property overrides for child documents
@@ -55,7 +56,7 @@ interface CollectionViewProps_ extends FieldViewProps {
childHideDecorationTitle?: () => boolean;
childHideResizeHandles?: () => boolean;
childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
- childCanEmbedOnDrag?: boolean;
+ childDragAction?: dropActionType;
childXPadding?: number;
childYPadding?: number;
childLayoutString?: string;
@@ -220,7 +221,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
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);
- isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() || this.isAnyChildContentActive();
+ isContentActive = (outsideReaction?: boolean) => (this.isAnyChildContentActive() ? true : this.props.isContentActive());
render() {
TraceMobx();
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 58605c3f4..13bb3a577 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -65,11 +65,7 @@ input.lm_title {
}
.miniMap {
- position: absolute;
overflow: hidden;
- right: 15;
- bottom: 15;
- border: solid 1px;
width: 100%;
height: 100%;
transition: all 0.5s;
@@ -90,18 +86,5 @@ input.lm_title {
cursor: pointer;
position: absolute;
bottom: 5;
- display: flex;
right: 5;
- width: 25px;
- height: 25px;
- border-radius: 3px;
- padding: 2px;
- justify-content: center;
- align-items: center;
- align-content: center;
- background-color: $light-gray;
-
- &:hover {
- box-shadow: none;
- }
}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 75e4e8abf..ea473d5cf 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { clamp } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableSet, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom/client';
import { Doc, Opt } from '../../../fields/Doc';
@@ -34,6 +34,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
import { CollectionView } from './CollectionView';
import './TabDocView.scss';
import React = require('react');
+import { Popup, Toggle, Type } from 'browndash-components';
const _global = (window /* browser */ || global) /* node */ as any;
interface TabDocViewProps {
@@ -43,12 +44,21 @@ interface TabDocViewProps {
}
@observer
export class TabDocView extends React.Component<TabDocViewProps> {
+ static _allTabs = new ObservableSet<TabDocView>();
_mainCont: HTMLDivElement | null = null;
_tabReaction: IReactionDisposer | undefined;
@observable _activated: boolean = false;
@observable _panelWidth = 0;
@observable _panelHeight = 0;
+ @observable _hovering = false;
@observable _isActive: boolean = false;
+ @observable _isAnyChildContentActive = false;
+ @computed get _isUserActivated() {
+ return SelectionManager.Views().some(view => view.rootDoc === this._document) || this._isAnyChildContentActive;
+ }
+ @computed get _isContentActive() {
+ return this._isUserActivated || this._hovering;
+ }
@observable _document: Doc | undefined;
@observable _view: DocumentView | undefined;
@@ -61,9 +71,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
return 'transparent';
}
@computed get tabColor() {
- let tabColor = StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor)));
- if (tabColor === 'transparent') return 'black';
- return tabColor;
+ return this._isUserActivated ? Colors.WHITE : this._hovering ? Colors.LIGHT_GRAY : Colors.MEDIUM_GRAY;
}
@computed get tabTextColor() {
return this._document?.type === DocumentType.PRES ? 'black' : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color)));
@@ -191,7 +199,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
});
tab._disposers.selectionDisposer = reaction(
- () => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc),
+ () => SelectionManager.Views().some(view => view.rootDoc === this._document),
action(selected => {
if (selected) this._activated = true;
const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
@@ -248,46 +256,47 @@ export class TabDocView extends React.Component<TabDocViewProps> {
return;
}
const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
- const pinDoc = Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc);
- pinDoc.presentationTargetDoc = anchorDoc ?? doc;
+ const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc);
+ pinDoc.presentation_targetDoc = anchorDoc ?? doc;
pinDoc.title = doc.title + ' - Slide';
pinDoc.data = new List<Doc>(); // the children of the embedding's layout are the presentation slide children. the embedding's 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 = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
- pinDoc.presDuration = pinDoc.presDuration ?? 1000;
- pinDoc.groupWithUp = false;
- pinDoc.embedContainer = curPres;
+ pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
+ pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000;
+ pinDoc.presentation_groupWithUp = false;
+ Doc.SetContainer(pinDoc, 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.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
+ pinDoc.treeView = ''; // not really needed, but makes key value pane look better
+ pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
+ pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
+ pinDoc.treeView_ChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
+ pinDoc.treeView_FieldKey = '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.treeView_ExpandedView = '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.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc);
if (!pinProps?.audioRange && duration !== undefined) {
pinDoc.mediaStart = 'manual';
pinDoc.mediaStop = 'manual';
- pinDoc.presStartTime = NumCast(doc.clipStart);
- pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
+ pinDoc.config_clipStart = NumCast(doc.clipStart);
+ pinDoc.config_clipEnd = NumCast(doc.clipEnd, duration);
}
if (pinProps?.activeFrame !== undefined) {
- pinDoc.presActiveFrame = pinProps?.activeFrame;
+ pinDoc.config_activeFrame = pinProps?.activeFrame;
pinDoc.title = doc.title + ' (move)';
- pinDoc.presMovement = PresMovement.Pan;
+ pinDoc.presentation_movement = PresMovement.Pan;
}
if (pinProps?.currentFrame !== undefined) {
- pinDoc.presCurrentFrame = pinProps?.currentFrame;
+ pinDoc.config_currentFrame = pinProps?.currentFrame;
pinDoc.title = doc.title + ' (move)';
- pinDoc.presMovement = PresMovement.Pan;
+ pinDoc.presentation_movement = PresMovement.Pan;
}
if (pinDoc.stroke_isInkMask) {
- pinDoc.presHideAfter = true;
- pinDoc.presHideBefore = true;
- pinDoc.presMovement = PresMovement.None;
+ pinDoc.presentation_hideAfter = true;
+ pinDoc.presentation_hideBefore = true;
+ pinDoc.presentation_movement = PresMovement.None;
}
- if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
+ if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true;
Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
PresBox.Instance?.clearSelectedArray();
pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
@@ -305,6 +314,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
}
+ @action
componentDidMount() {
new _global.ResizeObserver(
action((entries: any) => {
@@ -319,14 +329,17 @@ export class TabDocView extends React.Component<TabDocViewProps> {
// this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
// ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
// { fireImmediately: true });
+ TabDocView._allTabs.add(this);
}
componentDidUpdate() {
this._view && DocumentManager.Instance.AddView(this._view);
}
+ @action
componentWillUnmount() {
this._tabReaction?.();
this._view && DocumentManager.Instance.RemoveView(this._view);
+ TabDocView._allTabs.delete(this);
this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
}
@@ -335,6 +348,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
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;
+ if (!this._view) setTimeout(() => SelectionManager.SelectView(this._view, false));
!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.
}
}
@@ -348,10 +362,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
// lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox
addDocTab = (doc: Doc, location: OpenWhere) => {
SelectionManager.DeselectAll();
- const whereFields = doc._type_collection === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
+ const whereFields = location.split(':');
const keyValue = whereFields[1]?.includes('KeyValue');
const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
- if (doc.dockingConfig) return DashboardView.openDashboard(doc);
+ if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
// prettier-ignore
switch (whereFields[0]) {
case undefined:
@@ -364,8 +378,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
}
return LightboxView.AddDocTab(doc, location);
- case OpenWhere.dashboard: return DashboardView.openDashboard(doc);
- case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods);
case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue);
case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, undefined, keyValue);
@@ -382,10 +394,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
};
getCurrentFrame = () => {
- return NumCast(Cast(PresBox.Instance.activeItem.presentationTargetDoc, Doc, null)._currentFrame);
+ return NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
};
static Activate = (tabDoc: Doc) => {
- const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc);
+ const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.contentItem.config.props.keyValue);
tab?.header.parent.setActiveContentItem(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)
return tab !== undefined;
};
@@ -405,11 +417,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
};
PanelWidth = () => this._panelWidth;
PanelHeight = () => this._panelHeight;
- miniMapColor = () => this.tabColor;
+ miniMapColor = () => Colors.MEDIUM_GRAY;
tabView = () => this._view;
disableMinimap = () => !this._document || this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._type_collection !== CollectionViewType.Freeform;
- hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.layout_hideMinimap);
-
+ whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive);
+ isContentActive = () => this._isContentActive;
@computed get docView() {
return !this._activated || !this._document ? null : (
<>
@@ -427,7 +439,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
DataDoc={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
onBrowseClick={MainView.Instance.exploreMode}
waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick}
- isContentActive={returnTrue}
+ isContentActive={this.isContentActive}
isDocumentActive={returnFalse}
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
@@ -441,30 +453,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
ScreenToLocalTransform={this.ScreenToLocalTransform}
dontCenter={'y'}
rootSelected={returnTrue}
- whenChildContentsActiveChanged={emptyFunction}
+ whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
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.layout_hideMinimap ? 'Open minimap' : 'Close minimap'}</div>}>
- <div
- className="miniMap-hidden"
- style={{
- display: this.disableMinimap() || this._document._type_collection !== 'freeform' ? 'none' : undefined,
- color: this._document.layout_hideMinimap ? Colors.BLACK : Colors.WHITE,
- backgroundColor: this._document.layout_hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE,
- boxShadow: this._document.layout_hideMinimap ? Shadows.STANDARD_SHADOW : undefined,
- }}
- onPointerDown={e => e.stopPropagation()}
- onClick={action(e => {
- e.stopPropagation();
- this._document!.layout_hideMinimap = !this._document!.layout_hideMinimap;
- })}>
- <FontAwesomeIcon icon={'globe-asia'} size="lg" />
- </div>
- </Tooltip>
+ {this.disableMinimap() || this._document._type_collection !== CollectionViewType.Freeform ? null : (
+ <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
+ )}
</>
);
}
@@ -476,6 +473,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
style={{
fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
}}
+ onPointerEnter={action(() => (this._hovering = true))}
+ onPointerLeave={action(() => (this._hovering = false))}
+ onDragOver={action(() => (this._hovering = true))}
+ onDragLeave={action(() => (this._hovering = false))}
ref={ref => {
if ((this._mainCont = ref)) {
if (this._lastTab) {
@@ -495,7 +496,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
interface TabMinimapViewProps {
document: Doc;
- hideMinimap: () => boolean;
tabView: () => DocumentView | undefined;
addDocTab: (doc: Doc, where: OpenWhere) => boolean;
PanelWidth: () => number;
@@ -570,46 +570,56 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
if (!this.renderBounds) return null;
const miniWidth = (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
const miniHeight = (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / this.renderBounds.dim) * 100;
- const miniLeft = 50 + ((NumCast(this.props.document._freeform_) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
+ const miniLeft = 50 + ((NumCast(this.props.document._freeform_panX) - this.renderBounds.cx) / this.renderBounds.dim) * 100 - miniWidth / 2;
const miniTop = 50 + ((NumCast(this.props.document._freeform_panY) - this.renderBounds.cy) / this.renderBounds.dim) * 100 - miniHeight / 2;
const miniSize = this.returnMiniSize();
- return this.props.hideMinimap() ? null : (
- <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
- <CollectionFreeFormView
- Document={this.props.document}
- docViewPath={returnEmptyDoclist}
- childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
- noOverlay={true} // don't render overlay Docs since they won't scale
- setHeight={returnFalse}
- isContentActive={emptyFunction}
- isAnyChildContentActive={returnFalse}
- select={emptyFunction}
- dropAction={undefined}
- isSelected={returnFalse}
- dontRegisterView={true}
- fieldKey={Doc.LayoutFieldKey(this.props.document)}
- bringToFront={emptyFunction}
- rootSelected={returnTrue}
- addDocument={returnFalse}
- moveDocument={returnFalse}
- removeDocument={returnFalse}
- PanelWidth={this.returnMiniSize}
- PanelHeight={this.returnMiniSize}
- ScreenToLocalTransform={Transform.Identity}
- renderDepth={0}
- whenChildContentsActiveChanged={emptyFunction}
- focus={emptyFunction}
- styleProvider={TabMinimapView.miniStyleProvider}
- addDocTab={this.props.addDocTab}
- pinToPres={TabDocView.PinDoc}
- childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
- childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
- searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
- fitContentsToBox={returnTrue}
+ return (
+ <div className="miniMap-hidden">
+ <Popup
+ icon={<FontAwesomeIcon icon="globe-asia" size="lg" />}
+ color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)}
+ type={Type.TERT}
+ onPointerDown={e => e.stopPropagation()}
+ placement={'top-end'}
+ popup={
+ <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
+ <CollectionFreeFormView
+ Document={this.props.document}
+ docViewPath={returnEmptyDoclist}
+ childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
+ noOverlay={true} // don't render overlay Docs since they won't scale
+ setHeight={returnFalse}
+ isContentActive={emptyFunction}
+ isAnyChildContentActive={returnFalse}
+ select={emptyFunction}
+ isSelected={returnFalse}
+ dontRegisterView={true}
+ fieldKey={Doc.LayoutFieldKey(this.props.document)}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ PanelWidth={this.returnMiniSize}
+ PanelHeight={this.returnMiniSize}
+ ScreenToLocalTransform={Transform.Identity}
+ renderDepth={0}
+ whenChildContentsActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ styleProvider={TabMinimapView.miniStyleProvider}
+ addDocTab={this.props.addDocTab}
+ pinToPres={TabDocView.PinDoc}
+ childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+ childFiltersByRanges={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>
+ </div>
+ }
/>
- <div className="miniOverlay" onPointerDown={this.miniDown}>
- <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% ` }} />
- </div>
</div>
);
}
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 185bed74c..d3ba23b4e 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -13,7 +13,7 @@
.treeView-container-active {
.bullet-outline {
position: relative;
- width: $TREE_BULLET_WIDTH;
+ width: fit-content;
color: $medium-gray;
transform: scale(0.5);
display: inline-flex;
@@ -21,10 +21,9 @@
}
.treeView-bulletIcons {
- // width: $TREE_BULLET_WIDTH;
width: 100%;
height: 100%;
- position: absolute;
+ // position: absolute;
.treeView-expandIcon {
display: none;
@@ -43,15 +42,18 @@
display: unset;
}
}
+ position: relative;
+ display: flex;
+ flex-direction: row;
}
- .treeView-bulletIcons:hover img {
- left: 14px;
- position: absolute;
- transform-origin: center left;
- transform: scale(6);
- pointer-events: none;
- }
+ // .treeView-bulletIcons:hover img {
+ // left: 14px;
+ // position: absolute;
+ // transform-origin: center left;
+ // transform: scale(6);
+ // pointer-events: none;
+ // }
.bullet {
grid-column: 1;
@@ -59,11 +61,12 @@
justify-content: center;
align-items: center;
position: relative;
- width: $TREE_BULLET_WIDTH;
+ width: fit-content;
min-height: 20px;
color: $medium-gray;
border: #80808030 1px solid;
border-radius: 5px;
+ z-index: 1;
}
}
@@ -71,9 +74,6 @@
position: absolute;
height: max-content;
pointer-events: none;
- color: white;
- border-radius: 4px;
- font-size: 10px;
}
.treeView-container-active {
@@ -121,10 +121,26 @@
align-items: center;
width: max-content;
border-radius: 5px;
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+
+ .treeView-background {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 0;
+ filter: opacity(0);
+ }
&:hover {
- background-color: #bdddf5;
+ .treeView-background {
+ filter: opacity(0.2) !important;
+ }
}
+
//align-items: center;
::-webkit-scrollbar {
@@ -157,6 +173,7 @@
opacity: 0.75;
pointer-events: all;
cursor: pointer;
+ z-index: 1;
> svg {
margin-left: 0.25rem;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index f188c979c..a3725be75 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
@@ -21,7 +21,7 @@ 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 { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
import { EditableView } from '../EditableView';
import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
@@ -34,6 +34,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import './TreeView.scss';
import React = require('react');
+import { IconButton, Size } from 'browndash-components';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -45,7 +46,7 @@ export interface TreeViewProps {
dataDoc?: Doc;
treeViewParent: Doc;
renderDepth: number;
- dropAction: dropActionType;
+ dragAction: dropActionType;
addDocTab: (doc: Doc, where: OpenWhere) => boolean;
panelWidth: () => number;
panelHeight: () => number;
@@ -86,8 +87,8 @@ export enum TreeSort {
* 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
+ * treeView_Open : flag denoting whether the documents sub-tree (contents) is visible or hidden
+ * treeView_ExpandedView : name of field whose contents are being displayed as the document's subtree
*/
@observer
export class TreeView extends React.Component<TreeViewProps> {
@@ -103,16 +104,16 @@ export class TreeView extends React.Component<TreeViewProps> {
private _treedropDisposer?: DragManager.DragDropDisposer;
get treeViewOpenIsTransient() {
- return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsDataProto(this.doc);
+ return this.props.treeView.doc.treeView_OpenIsTransient || Doc.IsDataProto(this.doc);
}
set treeViewOpen(c: boolean) {
if (this.treeViewOpenIsTransient) this._transientOpenState = c;
else {
- this.doc.treeViewOpen = c;
+ this.doc.treeView_Open = c;
this._transientOpenState = false;
}
}
- @observable _transientOpenState = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
+ @observable _transientOpenState = false; // override of the treeView_Open field allowing the display state to be independent of the document's state
@observable _editTitle: boolean = false;
@observable _dref: DocumentView | undefined | null;
get displayName() {
@@ -126,34 +127,34 @@ export class TreeView extends React.Component<TreeViewProps> {
: this.props.treeView.fileSysMode
? this.doc.isFolder
? this.fieldKey
- : 'embeddings' // for displaying
+ : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list
: this.props.treeView.outlineMode || this.childDocs
? this.fieldKey
: Doc.noviceMode
? 'layout'
- : StrCast(this.props.treeView.doc.treeViewExpandedView, 'fields');
+ : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields');
}
@computed get doc() {
return this.props.document;
}
@computed get treeViewOpen() {
- return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeViewOpen', 'boolean', true)) || this._transientOpenState;
+ return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
}
@computed get treeViewExpandedView() {
- return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView;
+ return this.validExpandViewTypes.includes(StrCast(this.doc.treeView_ExpandedView)) ? StrCast(this.doc.treeView_ExpandedView) : this.defaultExpandedView;
}
@computed get MAX_EMBED_HEIGHT() {
return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
}
@computed get dataDoc() {
- return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DocData];
+ return this.props.document.treeView_ChildrenOnRoot ? this.doc : this.doc[DocData];
}
@computed get layoutDoc() {
return Doc.Layout(this.doc);
}
@computed get fieldKey() {
- return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc));
+ return StrCast(this.doc._treeView_FieldKey, Doc.LayoutFieldKey(this.doc));
}
@computed get childDocs() {
return this.childDocList(this.fieldKey);
@@ -221,12 +222,8 @@ export class TreeView extends React.Component<TreeViewProps> {
this.treeViewOpen = !this.treeViewOpen;
} else {
// choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding
- const bestEmbedding =
- docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document)
- ? docView.props.Document
- : DocListCast(this.props.document.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
- const nextBestEmbedding = DocListCast(this.props.document.proto_embeddings).find(doc => doc.author === Doc.CurrentUserEmail);
- this.props.addDocTab(bestEmbedding ?? nextBestEmbedding ?? Doc.MakeEmbedding(this.props.document), OpenWhere.lightbox);
+ const bestEmbedding = docView.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.rootDoc : Doc.BestEmbedding(docView.rootDoc);
+ this.props.addDocTab(bestEmbedding, OpenWhere.lightbox);
}
};
@@ -322,7 +319,7 @@ export class TreeView extends React.Component<TreeViewProps> {
};
onPointerEnter = (e: React.PointerEvent): void => {
this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
- if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
+ if (e.buttons === 1 && SnappingManager.GetIsDragging() && this.props.isContentActive()) {
this._header.current!.className = 'treeView-header';
document.removeEventListener('pointermove', this.onDragMove, true);
document.removeEventListener('pointerup', this.onDragUp, true);
@@ -357,13 +354,13 @@ export class TreeView extends React.Component<TreeViewProps> {
const bullet = Docs.Create.TextDocument('', {
layout: CollectionView.LayoutString('data'),
title: '-title-',
- treeViewExpandedViewLock: true,
- treeViewExpandedView: 'data',
+ treeView_ExpandedViewLock: true,
+ treeView_ExpandedView: 'data',
_type_collection: CollectionViewType.Tree,
layout_hideLinkButton: true,
_layout_showSidebar: true,
_layout_fitWidth: true,
- treeViewType: TreeViewType.outline,
+ treeView_Type: TreeViewType.outline,
x: 0,
y: 0,
_xMargin: 0,
@@ -387,13 +384,12 @@ export class TreeView extends React.Component<TreeViewProps> {
};
makeFolder = () => {
- const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true });
+ const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: 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) => {
+ preTreeDrop = (e: Event, de: DragManager.DropEvent) => {
const dragData = de.complete.docDragData;
dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
};
@@ -401,7 +397,7 @@ export class TreeView extends React.Component<TreeViewProps> {
@undoBatch
treeDrop = (e: Event, de: DragManager.DropEvent) => {
const pt = [de.x, de.y];
- if (!this._header.current) return;
+ if (!this._header.current) return false;
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 * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
@@ -410,14 +406,25 @@ export class TreeView extends React.Component<TreeViewProps> {
const destDoc = this.doc;
DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' });
e.stopPropagation();
+ return true;
}
const docDragData = de.complete.docDragData;
if (docDragData && pt[0] < rect.left + rect.width) {
if (docDragData.draggedDocuments[0] === this.doc) return true;
- if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.removeDocument, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) {
- e.stopPropagation();
- }
+ const added = this.dropDocuments(
+ docDragData.droppedDocuments, //
+ before,
+ inside,
+ docDragData.dropAction,
+ docDragData.removeDocument,
+ docDragData.moveDocument,
+ docDragData.treeViewDoc === this.props.treeView.props.Document
+ );
+ e.stopPropagation();
+ !added && e.preventDefault();
+ return added;
}
+ return false;
};
dropping: boolean = false;
@@ -427,17 +434,17 @@ export class TreeView extends React.Component<TreeViewProps> {
const innerAdd = (doc: Doc) => {
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
- dataIsComputed && (doc.embedContainer = this.doc.embedContainer);
+ dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
return added;
};
return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
};
const addDoc = inside ? localAdd : parentAddDoc;
const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument;
- const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeViewFreezeChildren).includes('add')) || forceAdd;
+ const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
if (canAdd) {
this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
- const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false));
+ const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
return res;
}
@@ -477,7 +484,7 @@ 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 === 'treeView_Open') continue;
const contents = doc[key];
let contentElement: (JSX.Element | null)[] | JSX.Element = [];
@@ -490,7 +497,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const innerAdd = (doc: Doc) => {
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- dataIsComputed && (doc.embedContainer = this.doc.embedContainer);
+ dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
return added;
};
return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
@@ -506,7 +513,7 @@ export class TreeView extends React.Component<TreeViewProps> {
addDoc,
remDoc,
moveDoc,
- this.props.dropAction,
+ this.props.dragAction,
this.props.addDocTab,
this.titleStyleProvider,
this.props.ScreenToLocalTransform,
@@ -575,9 +582,9 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get renderContent() {
TraceMobx();
const expandKey = this.treeViewExpandedView;
- const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }) ?? {};
+ const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) {
- const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
+ const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.None);
const sortKeys = Object.keys(sortings);
const curSortIndex = Math.max(
0,
@@ -589,7 +596,7 @@ export class TreeView extends React.Component<TreeViewProps> {
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);
+ const ordering = StrCast(this.doc.treeView_SortCriterion);
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;
@@ -598,7 +605,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
- !dataIsComputed && added && (doc.embedContainer = this.doc.embedContainer);
+ !dataIsComputed && added && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
return added;
};
@@ -615,28 +622,43 @@ export class TreeView extends React.Component<TreeViewProps> {
);
}
return (
- <>
+ <div>
{!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
- <div className={'treeView-sorting'} style={{ background: sortings[sorting]?.color }}>
- {sortings[sorting]?.label}
+ <div className={'treeView-sorting'}>
+ <IconButton
+ color={sortings[sorting]?.color}
+ size={Size.XSMALL}
+ icon={sortings[sorting]?.icon}
+ onPointerDown={e => {
+ downX = e.clientX;
+ downY = e.clientY;
+ e.stopPropagation();
+ }}
+ onClick={undoable(e => {
+ if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+ !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+ e.stopPropagation();
+ }
+ }, 'sort order')}
+ />
</div>
)}
<ul
style={{ cursor: 'inherit' }}
key={expandKey + 'more'}
title="click to change sort order"
- className={''} //this.doc.treeViewHideTitle ? 'no-indent' : ''}
+ className={''} //this.doc.treeView_HideTitle ? 'no-indent' : ''}
onPointerDown={e => {
downX = e.clientX;
downY = e.clientY;
e.stopPropagation();
}}
- onClick={e => {
+ onClick={undoable(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]);
+ !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
e.stopPropagation();
}
- }}>
+ }, 'sort order')}>
{!docs
? null
: TreeView.GetChildElements(
@@ -650,7 +672,7 @@ export class TreeView extends React.Component<TreeViewProps> {
addDoc,
remDoc,
moveDoc,
- StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType,
+ StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType,
this.props.addDocTab,
this.titleStyleProvider,
this.props.ScreenToLocalTransform,
@@ -675,7 +697,7 @@ export class TreeView extends React.Component<TreeViewProps> {
this._renderCount
)}
</ul>
- </>
+ </div>
);
} else if (this.treeViewExpandedView === 'fields') {
return (
@@ -686,6 +708,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
return (
<ul
+ style={{}}
onPointerDown={e => {
e.preventDefault();
e.stopPropagation();
@@ -706,7 +729,7 @@ export class TreeView extends React.Component<TreeViewProps> {
{
this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
heading: this.props.treeViewParent.title,
- checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check',
+ checked: this.doc.treeView_Checked === 'check' ? 'x' : this.doc.treeView_Checked === 'x' ? 'remove' : 'check',
containingTreeView: this.props.treeView.props.Document,
},
console.log
@@ -719,8 +742,9 @@ export class TreeView extends React.Component<TreeViewProps> {
@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;
+ const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
+ const color = StrCast(Doc.UserDoc().userColor);
+ const checked = this.onCheckedClick ? this.doc.treeView_Checked ?? 'unchecked' : undefined;
return (
<div
className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
@@ -740,7 +764,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}>
{this.props.treeView.outlineMode ? (
!(this.doc.text as RichTextField)?.Text ? null : (
- <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />
+ <IconButton color={color} icon={<FontAwesomeIcon icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} size={Size.XSMALL} />
)
) : (
<div className="treeView-bulletIcons" style={{ color: Doc.IsSystem(DocCast(this.doc.proto)) ? 'red' : undefined }}>
@@ -769,9 +793,9 @@ export class TreeView extends React.Component<TreeViewProps> {
}
@action
expandNextviewType = () => {
- if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeViewExpandedViewLock) {
+ if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeView_ExpandedViewLock) {
const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length];
- this.doc.treeViewExpandedView = next(this.validExpandViewTypes);
+ this.doc.treeView_ExpandedView = next(this.validExpandViewTypes);
}
this.treeViewOpen = true;
};
@@ -779,22 +803,20 @@ export class TreeView extends React.Component<TreeViewProps> {
@observable headerEleWidth = 0;
@computed get titleButtons() {
const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
- return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : (
+ const color = StrCast(Doc.UserDoc().userColor);
+ return this.props.treeViewHideHeaderFields() || this.doc.treeView_HideHeaderFields ? null : (
<>
{customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
- {this.doc._layout_hideContextMenu ? null : (
- <FontAwesomeIcon
- title="context menu"
- key="bars"
- icon="bars"
- size="sm"
- onClick={e => {
- this.showContextMenu(e);
- e.stopPropagation();
- }}
- />
- )}
- {Doc.noviceMode ? null : this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? null : (
+ <IconButton
+ color={color}
+ icon={<FontAwesomeIcon icon="bars" />}
+ size={Size.XSMALL}
+ onClick={e => {
+ this.showContextMenu(e);
+ e.stopPropagation();
+ }}
+ />
+ {Doc.noviceMode ? null : this.doc.treeView_ExpandedViewLock || Doc.IsSystem(this.doc) ? null : (
<span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
{this.treeViewExpandedView}
</span>
@@ -810,10 +832,10 @@ export class TreeView extends React.Component<TreeViewProps> {
};
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 folderOp = this.childDocs?.length ? [makeFolder] : [];
const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' };
const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
+ const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Reopen' };
return [
...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
...(this.doc.isFolder
@@ -824,7 +846,9 @@ export class TreeView extends React.Component<TreeViewProps> {
? [openEmbedding, makeFolder]
: this.doc._type_collection === CollectionViewType.Docking
? []
- : [deleteItem, openEmbedding, focusDoc]),
+ : this.props.treeView.rootDoc === Doc.MyRecentlyClosed
+ ? [reopenDoc]
+ : [openEmbedding, focusDoc]),
];
};
childContextMenuItems = () => {
@@ -836,7 +860,7 @@ export class TreeView extends React.Component<TreeViewProps> {
onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
- onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
+ onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeView_ChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {});
ignoreEvent = (e: any) => {
@@ -882,7 +906,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
- if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
+ if (this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
switch (e.key) {
case 'Tab':
e.stopPropagation?.();
@@ -907,7 +931,6 @@ export class TreeView extends React.Component<TreeViewProps> {
};
titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
- return18 = () => 18;
/**
* Renders the EditableView title element for placement into the tree.
*/
@@ -925,6 +948,7 @@ export class TreeView extends React.Component<TreeViewProps> {
height={12}
sizeToContent={true}
fontSize={12}
+ isEditingCallback={action(e => (this._editTitle = e))}
GetValue={() => StrCast(this.doc.title)}
OnTab={undoBatch((shift?: boolean) => {
if (!shift) this.props.indentDocument?.(true);
@@ -955,7 +979,6 @@ export class TreeView extends React.Component<TreeViewProps> {
hideDecorationTitle={this.props.treeView.outlineMode}
hideResizeHandles={this.props.treeView.outlineMode}
styleProvider={this.titleStyleProvider}
- enableDragWhenActive={true}
onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
docViewPath={this.props.treeView.props.docViewPath}
treeViewDoc={this.props.treeView.props.Document}
@@ -965,17 +988,18 @@ export class TreeView extends React.Component<TreeViewProps> {
pinToPres={emptyFunction}
onClick={this.onChildClick}
onDoubleClick={this.onChildDoubleClick}
- dropAction={this.props.dropAction}
+ dragAction={this.props.dragAction}
moveDocument={this.move}
removeDocument={this.props.removeDoc}
ScreenToLocalTransform={this.getTransform}
- NativeHeight={this.return18}
+ NativeHeight={return18}
NativeWidth={returnZero}
+ shouldNotScale={returnTrue}
PanelWidth={this.titleWidth}
- PanelHeight={this.return18}
+ PanelHeight={return18}
contextMenuItems={this.contextMenuItems}
renderDepth={1}
- isContentActive={this.props.isContentActive}
+ isContentActive={emptyFunction} //this.props.isContentActive}
isDocumentActive={this.props.isContentActive}
focus={this.refocus}
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
@@ -997,10 +1021,12 @@ export class TreeView extends React.Component<TreeViewProps> {
ref={this._tref}
title="click to edit title. Double Click or Drag to Open"
style={{
+ backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? StrCast(Doc.UserDoc().userVariantColor) : undefined,
+ color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(StrCast(Doc.UserDoc().userVariantColor)) : undefined,
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,
+ pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
}}>
{view}
</div>
@@ -1022,6 +1048,12 @@ export class TreeView extends React.Component<TreeViewProps> {
onPointerDown={this.ignoreEvent}
onPointerEnter={this.onPointerEnter}
onPointerLeave={this.onPointerLeave}>
+ <div
+ className="treeView-background"
+ style={{
+ background: StrCast(Doc.UserDoc().userColor),
+ }}
+ />
{contents}
</div>
{this.renderBorder}
@@ -1081,7 +1113,7 @@ export class TreeView extends React.Component<TreeViewProps> {
// 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.props.treeView.Document.treeViewHideUnrendered && this.doc.layout_unrendered && !this.doc.treeViewFieldKey ? (
+ return this.props.treeView.Document.treeView_HideUnrendered && this.doc.layout_unrendered && !this.doc.treeView_FieldKey ? (
<div></div>
) : (
<>
@@ -1102,7 +1134,7 @@ export class TreeView extends React.Component<TreeViewProps> {
};
@computed get renderBorder() {
- const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
+ const sorting = StrCast(this.doc.treeView_SortCriterion, 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 }}>
@@ -1122,7 +1154,7 @@ export class TreeView extends React.Component<TreeViewProps> {
render() {
TraceMobx();
- const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode;
+ const hideTitle = this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && 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
) : (
@@ -1134,9 +1166,9 @@ export class TreeView extends React.Component<TreeViewProps> {
// onKeyDown={this.onKeyDown}
>
<li className="collection-child">
- {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader // should test for prop 'treeViewRenderDocWithBulletAsHeader"
+ {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader"
? this.renderEmbeddedDocument(false, returnFalse)
- : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
+ : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
</li>
</div>
);
@@ -1175,21 +1207,21 @@ export class TreeView extends React.Component<TreeViewProps> {
childDocs: Doc[],
treeView: CollectionTreeView,
parentTreeView: CollectionTreeView | TreeView | undefined,
- treeViewParent: Doc,
+ treeView_Parent: Doc,
dataDoc: Doc | undefined,
parentCollectionDoc: Doc | undefined,
containerPrevSibling: Doc | undefined,
add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean,
remove: undefined | ((doc: Doc | Doc[]) => boolean),
move: DragManager.MoveFunction,
- dropAction: dropActionType,
+ dragAction: dropActionType,
addDocTab: (doc: Doc, where: OpenWhere) => boolean,
styleProvider: undefined | StyleProviderFunc,
screenToLocalXf: () => Transform,
isContentActive: (outsideReaction?: boolean) => boolean,
panelWidth: () => number,
renderDepth: number,
- treeViewHideHeaderFields: () => boolean,
+ treeView_HideHeaderFields: () => boolean,
renderedIds: string[],
onCheckedClick: undefined | (() => ScriptField),
onChildClick: undefined | (() => ScriptField),
@@ -1206,19 +1238,19 @@ export class TreeView extends React.Component<TreeViewProps> {
hierarchyIndex?: number[],
renderCount?: number
) {
- const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField);
+ const viewSpecScript = Cast(treeView_Parent.viewSpecScript, ScriptField);
if (viewSpecScript) {
childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
}
- const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeViewSortCriterion, TreeSort.None));
+ const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.None));
const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
- const treeViewRefs = new Map<Doc, TreeView | undefined>();
+ const treeView_Refs = new Map<Doc, TreeView | undefined>();
return docs
.filter(child => child instanceof Doc)
.map((child, i) => {
if (renderCount && i > renderCount) return null;
- const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
+ const pair = Doc.GetLayoutDataDocPair(treeView_Parent, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return null;
}
@@ -1231,11 +1263,11 @@ export class TreeView extends React.Component<TreeViewProps> {
FormattedTextBox.SelectOnLoad = child[Id];
TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
- newParent.treeViewOpen = true;
- child.embedContainer = treeView.Document;
+ newParent.treeView_Open = true;
+ Doc.SetContainer(child, treeView.Document);
}
};
- const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
+ const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1]));
const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
const childLayout = Doc.Layout(pair.layout);
@@ -1246,10 +1278,10 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<TreeView
key={child[Id]}
- ref={r => treeViewRefs.set(child, r ? r : undefined)}
+ ref={r => treeView_Refs.set(child, r ? r : undefined)}
document={pair.layout}
dataDoc={pair.data}
- treeViewParent={treeViewParent}
+ treeViewParent={treeView_Parent}
prevSibling={docs[i]}
// TODO: [AL] add these
hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
@@ -1261,18 +1293,18 @@ export class TreeView extends React.Component<TreeViewProps> {
onCheckedClick={onCheckedClick}
onChildClick={onChildClick}
renderDepth={renderDepth}
- removeDoc={StrCast(treeViewParent.treeViewFreezeChildren).includes('remove') ? undefined : remove}
+ removeDoc={StrCast(treeView_Parent.treeView_FreezeChildren).includes('remove') ? undefined : remove}
addDocument={addDocument}
styleProvider={styleProvider}
panelWidth={rowWidth}
panelHeight={rowHeight}
dontRegisterView={dontRegisterView}
moveDocument={move}
- dropAction={dropAction}
+ dragAction={dragAction}
addDocTab={addDocTab}
ScreenToLocalTransform={screenToLocalXf}
isContentActive={isContentActive}
- treeViewHideHeaderFields={treeViewHideHeaderFields}
+ treeViewHideHeaderFields={treeView_HideHeaderFields}
renderedIds={renderedIds}
skipFields={skipFields}
firstLevel={firstLevel}
@@ -1289,6 +1321,6 @@ export class TreeView extends React.Component<TreeViewProps> {
ScriptingGlobals.add(function TreeView_addNewFolder() {
TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
- const opts = { title: 'Untitled folder', _stayInCollection: true, isFolder: true };
+ const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true };
return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f1d98d22a..fb8ec93b2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -235,6 +235,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
bActive,
textX,
textY,
+ // pt1,
+ // pt2,
+ // this code adds space between links
pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
};
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
index cb5cef29c..4ada1731f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -1,11 +1,13 @@
-.collectionfreeformlinksview-svgCanvas{
+// TODO: change z-index to -1 when a modal is active?
+
+.collectionfreeformlinksview-svgCanvas {
position: absolute;
top: 0;
left: 0;
- width: 100%;
+ width: 100%;
height: 100%;
pointer-events: none;
- }
- .collectionfreeformlinksview-container {
+}
+.collectionfreeformlinksview-container {
pointer-events: none;
- } \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index c6419885b..e4ae251c8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -153,7 +153,12 @@
overflow-y: auto;
overflow-x: hidden;
}
-
+.collectionFreeFormView-brushView {
+ pointer-events: none;
+ position: absolute;
+ transition: opacity 0.5s;
+ z-index: 1000;
+}
.collectionfreeformview-container {
// touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
touch-action: none;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 11151e74e..ffcf0999c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -319,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// focus on the document in the collection
const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
if (didMove) options.didMove = true;
- // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
+ // glr: freeform transform speed can be set by adjusting presentation_transition field - needs a way of knowing when presentation is not active...
if (didMove) {
const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
(options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
@@ -418,15 +418,16 @@ 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.embedContainer !== this.props.Document) {
- // if the source doc view's embedContainer isn't this same freeformcollectionlinkDragData.dragDocument.embedContainer === this.props.Document
+ let added = false;
+ // do nothing if link is dropped into any freeform view parent of dragged document
+ if (!linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.rootDoc) {
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
- this.props.addDocument?.(source);
+ added = this.props.addDocument?.(source) ? true : false;
de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: '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
- return true;
+ e.stopPropagation();
+ !added && e.preventDefault();
+ return added;
}
return false;
}
@@ -680,6 +681,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
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);
+ console.log(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = Docs.Create.InkDocument(
ActiveInkColor(),
ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
@@ -1049,10 +1051,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onPointerWheel = (e: React.WheelEvent): void => {
if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
PresBox.Instance?.pauseAutoPres();
- if (this.layoutDoc._Transform || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
+ if (this.layoutDoc._Transform || this.props.Document.treeView_OutlineMode === TreeViewType.outline) return;
e.stopPropagation();
const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
- const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
+ const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4;
switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
case freeformScrollMode.Pan:
// if ctrl is selected then zoom
@@ -1240,7 +1242,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
};
- isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isContentActive = () => this.props.isContentActive();
@undoBatch
@action
@@ -1263,13 +1265,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.addDocument?.(newDoc);
}
};
- pointerEvents = () => {
+ @computed get _pointerEvents() {
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
const pointerEvents = DocumentDecorations.Instance.Interacting
? 'none'
- : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+ : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
return pointerEvents;
- };
+ }
+ pointerEvents = () => this._pointerEvents;
+ childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
@@ -1297,8 +1301,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
- isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={this.props.childContentsActive ?? emptyFunction}
+ isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive}
+ isContentActive={this.childContentsActive}
focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
addDocTab={this.addDocTab}
addDocument={this.props.addDocument}
@@ -1308,16 +1312,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
docViewPath={this.props.docViewPath}
styleProvider={this.getClusterColor}
- canEmbedOnDrag={this.props.childCanEmbedOnDrag}
+ dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
dataProvider={this.childDataProvider}
sizeProvider={this.childSizeProvider}
- dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
layout_showTitle={this.props.childlayout_showTitle}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.pointerEvents}
- //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
- //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeViewFreezeChildDimensions)} // bcz: check this
+ //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeView_FreezeChildDimensions)} // bcz: check this
/>
);
}
@@ -1356,6 +1358,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _lightboxDoc: Opt<Doc>;
getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
+ 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);
const childDoc = params.pair.layout;
const childDocLayout = Doc.Layout(childDoc);
const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
@@ -1366,11 +1369,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
layoutFrameNumber === undefined
? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
: CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
+ // prettier-ignore
+ const rotation = Cast(_rotation,'number',
+ !this.layoutDoc._rotation_jitter ? null
+ : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
return {
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
- rotation: Cast(_rotation, 'number'),
+ rotation: rotation,
color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
@@ -1435,21 +1442,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}.bind(this)
);
- childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
- return this._layoutPoolData.get(doc[Id] + (replica || ''));
- };
+ childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || ''));
childDataProvider = computedFn(
function childDataProvider(this: any, doc: Doc, replica: string) {
- return this._layoutPoolData.get(doc[Id] + (replica || ''));
+ return this.childPositionProviderUnmemoized(doc, replica);
}.bind(this)
);
- childSizeProviderUnmemoized = (doc: Doc, replica: string) => {
- return this._layoutSizeData.get(doc[Id] + (replica || ''));
- };
+ childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || ''));
childSizeProvider = computedFn(
function childSizeProvider(this: any, doc: Doc, replica: string) {
- return this._layoutSizeData.get(doc[Id] + (replica || ''));
+ return this.childSizeProviderUnmemoized(doc, replica);
}.bind(this)
);
@@ -1535,7 +1538,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
- const anchor = Docs.Create.CollectionConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
+ const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.rootDoc });
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc);
if (addAsAnnotation) {
@@ -1864,6 +1867,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null);
+ brushedView = () => this._brushedView;
@computed get marqueeView() {
TraceMobx();
return (
@@ -1906,13 +1910,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
</div>
) : null}
<CollectionFreeFormViewPannableContents
- brushView={this._brushedView}
+ brushedView={this.brushedView}
isAnnotationOverlay={this.isAnnotationOverlay}
isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
transform={this.contentTransform}
zoomScaling={this.zoomScaling}
presPaths={this.showPresPaths}
- presPinView={BoolCast(this.Document.presPinView)}
+ presPinView={BoolCast(this.Document.config_pinView)}
transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
viewDefDivClick={this.props.viewDefDivClick}>
{this.children}
@@ -1943,19 +1947,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
- brushView = (viewport: { width: number; height: number; panX: number; panY: number }) => {
- this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2, opacity: 1 };
+ brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number) => {
this._brushtimer1 && clearTimeout(this._brushtimer1);
this._brushtimer && clearTimeout(this._brushtimer);
+ this._brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 };
this._brushtimer1 = setTimeout(
action(() => {
- this._brushedView.opacity = 0;
+ this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2, opacity: 1 };
this._brushtimer = setTimeout(
- action(() => (this._brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 })),
- 500
+ action(() => (this._brushedView.opacity = 0)),
+ 2500
);
}),
- 1000
+ transTime + 1
);
};
lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
@@ -1987,7 +1991,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) ? 'all' : (this.props.pointerEvents?.() as any),
+ pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this.props.pointerEvents?.() as any),
textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
transform: `scale(${this.nativeDimScaling || 1})`,
width: `${100 / (this.nativeDimScaling || 1)}%`,
@@ -2082,7 +2086,7 @@ interface CollectionFreeFormViewPannableContentsProps {
presPinView?: boolean;
isAnnotationOverlay: boolean | undefined;
isAnnotationOverlayScrollable: boolean | undefined;
- brushView: { panX: number; panY: number; width: number; height: number; opacity: number };
+ brushedView: () => { panX: number; panY: number; width: number; height: number; opacity: number };
}
@observer
@@ -2154,6 +2158,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
}
render() {
+ const brushedView = this.props.brushedView();
return (
<div
className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
@@ -2171,21 +2176,18 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
//willChange: "transform"
}}>
{this.props.children}
- {!this.props.brushView.width ? null : (
+ {
<div
className="collectionFreeFormView-brushView"
style={{
- zIndex: 1000,
- opacity: this.props.brushView.opacity,
- border: 'orange solid 2px',
- position: 'absolute',
- transform: `translate(${this.props.brushView.panX}px, ${this.props.brushView.panY}px)`,
- width: this.props.brushView.width,
- height: this.props.brushView.height,
- transition: 'opacity 2s',
+ opacity: brushedView.opacity,
+ transform: `translate(${brushedView.panX}px, ${brushedView.panY}px)`,
+ width: brushedView.width,
+ height: brushedView.height,
+ border: `orange solid ${brushedView.width * 0.005}px`,
}}
/>
- )}
+ }
{this.presPaths}
</div>
);
@@ -2256,22 +2258,20 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
const browseTransitionTime = 500;
SelectionManager.DeselectAll();
- if (
- dv.props.focus(dv.props.Document, {
- willZoomCentered: true,
- zoomScale: 0.8,
- zoomTime: browseTransitionTime,
- }) === undefined
- ) {
- const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
- let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 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, browseTransitionTime);
- Doc.linkFollowHighlight(dv?.props.Document, false);
- } else {
- DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true });
- }
+ DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
+ if (!focused) {
+ const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+ let containers = dv.props.docViewPath();
+ let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ for (var cont of containers) {
+ parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ }
+ while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 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, browseTransitionTime);
+ Doc.linkFollowHighlight(dv?.props.Document, false);
+ }
+ });
}
ScriptingGlobals.add(CollectionBrowseClick);
ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index c9168d40a..0f51fe6ff 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -4,6 +4,10 @@ import { Tooltip } from '@material-ui/core';
import { observer } from 'mobx-react';
import { unimplementedFunction } from '../../../../Utils';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { IconButton } from 'browndash-components';
+import { StrCast } from '../../../../fields/Types';
+import { Doc } from '../../../../fields/Doc';
+import { computed } from 'mobx';
@observer
export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -22,39 +26,44 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
MarqueeOptionsMenu.Instance = this;
}
+ @computed get userColor() {
+ return StrCast(Doc.UserDoc().userColor)
+ }
+
render() {
- const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 19, transform: 'translate(-2px, -2px)' }} />;
+ const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />;
const buttons = (
<>
- <Tooltip key="collect" title={<div className="dash-tooltip">Create a Collection</div>} placement="bottom">
- <button className="antimodeMenu-button" onPointerDown={this.createCollection}>
- <FontAwesomeIcon icon="object-group" size="lg" />
- </button>
- </Tooltip>
- ,
- <Tooltip key="group" title={<div className="dash-tooltip">Create a Grouping</div>} placement="bottom">
- <button className="antimodeMenu-button" onPointerDown={e => this.createCollection(e, true)}>
- <FontAwesomeIcon icon="layer-group" size="lg" />
- </button>
- </Tooltip>
- ,
- <Tooltip key="summarize" title={<div className="dash-tooltip">Summarize Documents</div>} placement="bottom">
- <button className="antimodeMenu-button" onPointerDown={this.summarize}>
- <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
- </button>
- </Tooltip>
- ,
- <Tooltip key="delete" title={<div className="dash-tooltip">Delete Documents</div>} placement="bottom">
- <button className="antimodeMenu-button" onPointerDown={this.delete}>
- <FontAwesomeIcon icon="trash-alt" size="lg" />
- </button>
- </Tooltip>
- ,
- <Tooltip key="pinWithView" title={<div className="dash-tooltip">Pin selected region to trail</div>} placement="bottom">
- <button className="antimodeMenu-button" onPointerDown={this.pinWithView}>
- {presPinWithViewIcon}
- </button>
- </Tooltip>
+ <IconButton
+ tooltip={"Create a Collection"}
+ onPointerDown={this.createCollection}
+ icon={<FontAwesomeIcon icon="object-group"/>}
+ color={this.userColor}
+ />
+ <IconButton
+ tooltip={"Create a Grouping"}
+ onPointerDown={e => this.createCollection(e, true)}
+ icon={<FontAwesomeIcon icon="layer-group"/>}
+ color={this.userColor}
+ />
+ <IconButton
+ tooltip={"Summarize Documents"}
+ onPointerDown={this.summarize}
+ icon={<FontAwesomeIcon icon="compress-arrows-alt"/>}
+ color={this.userColor}
+ />
+ <IconButton
+ tooltip={"Delete Documents"}
+ onPointerDown={this.delete}
+ icon={<FontAwesomeIcon icon="trash-alt"/>}
+ color={this.userColor}
+ />
+ <IconButton
+ tooltip={"Pin selected region"}
+ onPointerDown={this.pinWithView}
+ icon={presPinWithViewIcon}
+ color={this.userColor}
+ />
</>
);
return this.getElement(buttons);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 534275610..1c3da1dc5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -9,7 +9,7 @@ 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 { distributeAcls, GetEffectiveAcl, SharingPermissions } from '../../../../fields/util';
import { intersectRect, returnFalse, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
@@ -161,7 +161,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
})
);
- } else if (e.key === 's' && e.ctrlKey) {
+ } /* else if (e.key === 's' && e.ctrlKey) {
e.preventDefault();
const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!;
slide.x = x;
@@ -170,6 +170,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
this.props.addDocument?.(slide);
e.stopPropagation();
+ }*/ else if (e.key === 'p' && e.ctrlKey) {
+ e.preventDefault();
+ (async () => {
+ const text: string = await navigator.clipboard.readText();
+ const ns = text.split('\n').filter(t => t.trim() !== '\r' && t.trim() !== '');
+ this.pasteTable(ns, x, y);
+ })();
+ 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('type new note');
@@ -185,44 +193,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) {
- ns.splice(0, 1);
- }
- if (ns.length > 0) {
- const columns = ns[0].split('\t');
- const docList: Doc[] = [];
- let groupAttr: string | number = '';
- const rowProto = new Doc();
- rowProto.title = rowProto.Id;
- rowProto._width = 200;
- rowProto.isDataDoc = true;
- for (let i = 1; i < ns.length - 1; i++) {
- const values = ns[i].split('\t');
- if (values.length === 1 && columns.length > 1) {
- groupAttr = values[0];
- continue;
- }
- const docDataProto = Doc.MakeDelegate(rowProto);
- docDataProto.isDataDoc = 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));
- if (groupAttr) {
- docDataProto._group = groupAttr;
- }
- docDataProto.title = i.toString();
- const doc = Doc.MakeDelegate(docDataProto);
- doc._width = 200;
- docList.push(doc);
+ let csvRows = [];
+ const headers = ns[0].split('\t');
+ csvRows.push(headers.join(','));
+ ns[0] = '';
+ const eachCell = ns.join('\t').split('\t')
+ let eachRow = []
+ for (let i=1; i<eachCell.length; i++){
+ eachRow.push(eachCell[i].replace(/\,/g, ''));
+ if (i % headers.length == 0){
+ csvRows.push(eachRow)
+ eachRow = [];
}
- 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);
}
+
+ const blob = new Blob([csvRows.join('\n')], {type: 'text/csv'})
+ const options = { x: x, y: y, title: 'droppedTable', _width: 300, _height: 100, type:'text/csv'}
+ const file = new File([blob], 'droppedTable', options);
+ const loading = Docs.Create.LoadingDocument(file, options);
+ DocUtils.uploadFileToDoc(file, {}, loading);
+ this.props.addDocument?.(loading);
}
@action
@@ -337,7 +327,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onClick = (e: React.MouseEvent): void => {
if (this.props.pointerEvents?.() === 'none') return;
- if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
if (Doc.ActiveTool === InkTool.None) {
if (!(e.nativeEvent as any).marqueeHit) {
(e.nativeEvent as any).marqueeHit = true;
@@ -377,7 +367,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
: ((doc: Doc) => {
Doc.GetProto(doc).data = new List<Doc>(selected);
Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
- !this.props.isAnnotationOverlay && Doc.AddFileOrphan(Doc.GetProto(doc));
doc._freeform_panX = doc._freeform_panY = 0;
return doc;
})(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
@@ -385,12 +374,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
newCollection._width = this.Bounds.width;
newCollection._height = this.Bounds.height;
newCollection._isGroup = makeGroup;
+ newCollection._dragWhenActive = makeGroup;
newCollection.forceActive = makeGroup;
- newCollection.enableDragWhenActive = makeGroup;
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
newCollection.layout_fitWidth = true;
- selected.forEach(d => (d.embedContainer = newCollection));
+ selected.forEach(d => Doc.SetContainer(d, newCollection));
this.hideMarquee();
return newCollection;
});
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index e8ae88ae5..cd8b7a0cc 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -368,7 +368,7 @@ export class CollectionGridView extends CollectionSubView() {
<div
className="collectionGridView-contents"
ref={this.createDashEventsTarget}
- style={{ pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined }}
+ style={{ pointerEvents: !this.props.isContentActive() ? 'none' : undefined }}
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
onDrop={this.onExternalDrop}>
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 3e3709827..6b3318bf3 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -17,12 +17,7 @@
.collectionLinearView-menuOpener {
user-select: none;
}
-
- &.true {
- border-left: $standard-border;
- background-color: $medium-blue-alt;
- }
-
+
> input:not(:checked) ~ &.true {
background-color: transparent;
}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 65578f214..2254b2e5f 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Opt } from '../../../../fields/Doc';
@@ -20,6 +20,8 @@ import { UndoStack } from '../../UndoStack';
import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionLinearView.scss';
+import { Button, Toggle, ToggleType, Type } from 'browndash-components';
+import { Colors } from '../../global/globalEnums';
/**
* CollectionLinearView is the class for rendering the horizontal collection
@@ -46,38 +48,14 @@ export class CollectionLinearView extends CollectionSubView() {
componentDidMount() {
this._widthDisposer = reaction(
- () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
+ () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
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(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
- }
- });
- if (selected && selected.layout) {
- ScriptCast(DocCast(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
- this._dropDisposer && this._dropDisposer();
- if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
- }
+ this._dropDisposer?.();
+ if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
};
dimension = () => NumCast(this.rootDoc._height);
@@ -89,12 +67,8 @@ export class CollectionLinearView extends CollectionSubView() {
@action
exitLongLinks = () => {
- if (DocumentLinksButton.StartLink) {
- if (DocumentLinksButton.StartLink.Document) {
- action((e: React.PointerEvent<HTMLDivElement>) => {
- Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
- });
- }
+ if (DocumentLinksButton.StartLink?.Document) {
+ action((e: React.PointerEvent<HTMLDivElement>) => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
}
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
@@ -201,7 +175,7 @@ export class CollectionLinearView extends CollectionSubView() {
moveDocument={this.props.moveDocument}
addDocTab={this.props.addDocTab}
pinToPres={emptyFunction}
- dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
+ dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
rootSelected={this.props.isSelected}
removeDocument={this.props.removeDocument}
ScreenToLocalTransform={docXf}
@@ -226,54 +200,45 @@ export class CollectionLinearView extends CollectionSubView() {
render() {
const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content
const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content
- const isExpanded = BoolCast(this.layoutDoc.linearView_IsExpanded);
+ const isExpanded = BoolCast(this.layoutDoc.linearView_IsOpen);
const menuOpener = (
- <label
- className={`collectionlinearView-label${isExpanded ? '-expanded' : ''}`}
- htmlFor={this.Document[Id] + '-input'}
- style={{ boxShadow: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BoxShadow) }}
- onPointerDown={StopEvent}>
- <div className="collectionLinearView-menuOpener">{Cast(this.props.Document.icon, 'string', null) ?? <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />}</div>
- </label>
+ <Toggle
+ text={Cast(this.props.Document.icon, 'string', null)}
+ icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />}
+ type={Type.TERT}
+ color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)}
+ onPointerDown={e => e.stopPropagation()}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)}
+ onClick={() => {
+ this.layoutDoc.linearView_IsOpen = !isExpanded;
+ }}
+ tooltip={isExpanded ? 'Close' : 'Open'}
+ fillWidth={true}
+ align={'center'}
+ />
);
return (
- <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsExpanded ? undefined : 'transparent' }}>
- <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension() }}>
- {!this.props.Document.linearView_Expandable ? null : (
- <Tooltip title={<div className="dash-tooltip">{isExpanded ? 'Close' : 'Open'}</div>} placement="top">
+ <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}>
+ <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
+ {
+ <>
{menuOpener}
- </Tooltip>
- )}
- <input
- id={this.Document[Id] + '-input'}
- type="checkbox"
- checked={isExpanded}
- ref={this.addMenuToggle}
- onChange={action(e => {
- ScriptCast(this.Document.onClick)?.script.run({
- this: this.layoutDoc,
- self: this.rootDoc,
- _readOnly_: false,
- scriptContext: this.props.scriptContext,
- documentView: this.props.DocumentView?.(),
- });
- this.layoutDoc.linearView_IsExpanded = this.addMenuToggle.current!.checked;
- })}
- />
-
- {!this.layoutDoc.linearView_IsExpanded ? null : (
- <div
- className="collectionLinearView-content"
- style={{
- height: this.dimension(),
- flexDirection: flexDir as any,
- gap: flexGap,
- }}>
- {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
- </div>
- )}
+ {!this.layoutDoc.linearView_IsOpen ? null : (
+ <div
+ className="collectionLinearView-content"
+ style={{
+ height: this.dimension(),
+ flexDirection: flexDir as any,
+ gap: flexGap,
+ }}>
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+ </div>
+ )}
+ </>
+ }
</div>
</div>
);
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index 821c8d804..f87a06033 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -8,6 +8,11 @@
display: flex;
flex-direction: column;
width: 100%;
+ align-items: center;
+
+ .contentFittingDocumentView {
+ width: unset;
+ }
.label-wrapper {
display: flex;
@@ -15,7 +20,6 @@
justify-content: center;
height: 20px;
}
-
}
.multiColumnResizer {
@@ -30,5 +34,4 @@
transition: 0.5s background-color ease;
}
}
-
-} \ 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 34fa0343d..10532b9d9 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -5,7 +5,6 @@ import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
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';
@@ -194,11 +193,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
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);
- })
- );
+ de.complete.docDragData?.droppedDocuments.forEach(d => (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) {
@@ -225,6 +220,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
}
})
);
+ return true;
}
}
return false;
@@ -234,8 +230,15 @@ export class CollectionMulticolumnView extends CollectionSubView() {
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
- isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false);
- getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
+ isChildContentActive = () => {
+ const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
+ return this.props.isContentActive?.() === false || childDocsActive === false
+ ? false //
+ : this.props.isDocumentActive?.() && childDocsActive
+ ? true
+ : undefined;
+ };
+ getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => {
return (
<DocumentView
Document={layout}
@@ -247,8 +250,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
+ shouldNotScale={shouldNotScale}
rootSelected={this.rootSelected}
- dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+ dragAction={(this.props.Document.childDragAction ?? this.props.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
suppressSetHeight={true}
@@ -284,15 +288,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
const collector: JSX.Element[] = [];
for (let i = 0; i < childLayoutPairs.length; i++) {
const { layout } = childLayoutPairs[i];
+ const aspect = Doc.NativeAspect(layout, undefined, true);
+ const width = () => this.lookupPixels(layout);
+ const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
+ const docwidth = () => (layout._layout_forceReflow ? width() : Math.min(height() * aspect, width()));
+ const docheight = () => Math.min(docwidth() / aspect, height());
const dxf = () =>
this.lookupIndividualTransform(layout)
- .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))
+ .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -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);
+ const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
collector.push(
- <div className={'document-wrapper'} key={'wrapper' + i} style={{ width: width() }}>
- {this.getDisplayDoc(layout, dxf, width, height)}
+ <div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}>
+ {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
<WidthLabel layout={layout} collectionDoc={Document} />
</div>,
<ResizeBar
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index 79fb195e8..ec7200a03 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -9,6 +9,12 @@
display: flex;
flex-direction: row;
height: 100%;
+ align-items: center;
+ margin: auto;
+
+ .contentFittingDocumentView {
+ height: unset;
+ }
.label-wrapper {
display: flex;
@@ -16,7 +22,6 @@
justify-content: center;
height: 20px;
}
-
}
.multiRowResizer {
@@ -31,5 +36,4 @@
transition: 0.5s background-color ease;
}
}
-
-} \ 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 bab77ed48..04cfc5456 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -5,7 +5,6 @@ import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
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';
@@ -194,11 +193,7 @@ export class CollectionMultirowView extends CollectionSubView() {
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);
- })
- );
+ de.complete.docDragData?.droppedDocuments.forEach(d => (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) {
@@ -225,6 +220,7 @@ export class CollectionMultirowView extends CollectionSubView() {
}
})
);
+ return true;
}
}
return false;
@@ -234,8 +230,15 @@ export class CollectionMultirowView extends CollectionSubView() {
onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
- isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false);
- getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
+ isChildContentActive = () => {
+ const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
+ return this.props.isContentActive?.() === false || childDocsActive === false
+ ? false //
+ : this.props.isDocumentActive?.() && childDocsActive
+ ? true
+ : undefined;
+ };
+ getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => {
return (
<DocumentView
Document={layout}
@@ -247,8 +250,9 @@ export class CollectionMultirowView extends CollectionSubView() {
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
+ shouldNotScale={shouldNotScale}
rootSelected={this.rootSelected}
- dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+ dropAction={StrCast(this.rootDoc.childDragAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
@@ -257,6 +261,7 @@ export class CollectionMultirowView extends CollectionSubView() {
hideResizeHandles={this.props.childHideResizeHandles?.()}
hideDecorationTitle={this.props.childHideDecorationTitle?.()}
fitContentsToBox={this.props.fitContentsToBox}
+ dragAction={this.props.childDragAction}
focus={this.props.focus}
childFilters={this.childDocFilters}
childFiltersByRanges={this.childDocRangeFilters}
@@ -283,15 +288,19 @@ export class CollectionMultirowView extends CollectionSubView() {
const collector: JSX.Element[] = [];
for (let i = 0; i < childLayoutPairs.length; i++) {
const { layout } = childLayoutPairs[i];
+ const aspect = Doc.NativeAspect(layout, undefined, true);
+ const height = () => this.lookupPixels(layout);
+ const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
+ const docheight = () => Math.min(width() / aspect, height());
+ const docwidth = () => (layout._layout_forceReflow ? width() : Math.min(width(), docheight() * aspect));
const dxf = () =>
this.lookupIndividualTransform(layout)
- .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin))
+ .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin) - (height() - docheight()) / 2)
.scale(this.props.NativeDimScaling?.() || 1);
- const height = () => this.lookupPixels(layout);
- const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
+ const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
collector.push(
- <div className={'document-wrapper'} style={{ height: height() }} key={'wrapper' + i}>
- {this.getDisplayDoc(layout, dxf, width, height)}
+ <div className="document-wrapper" style={{ height: height() }} key={'wrapper' + i}>
+ {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
<HeightLabel layout={layout} collectionDoc={Document} />
</div>,
<ResizeBar
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 15424de98..babe5c810 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -434,7 +434,6 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.columnDragData) {
- e.stopPropagation();
const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
const index = this.findDropIndex(mouseX);
this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex);
@@ -448,6 +447,7 @@ export class CollectionSchemaView extends CollectionSubView() {
});
});
+ e.stopPropagation();
return true;
}
const draggedDocs = de.complete.docDragData?.draggedDocuments;
@@ -462,7 +462,6 @@ export class CollectionSchemaView extends CollectionSubView() {
if (draggedView) DocumentManager.Instance.RemoveView(draggedView);
DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true));
});
- e.stopPropagation();
return true;
}
return false;
@@ -811,7 +810,6 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get sortedDocs() {
- trace();
const field = StrCast(this.layoutDoc.sortField);
const desc = BoolCast(this.layoutDoc.sortDesc);
const docs = !field
@@ -952,7 +950,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
styleProvider={DefaultStyleProvider}
waitForDoubleClickToClick={returnNever}
defaultDoubleClick={returnIgnore}
- enableDragWhenActive={true}
+ dragAction="move"
onClickScriptDisable="always"
focus={this.props.schema.focusDocument}
childFilters={this.props.schema.childDocFilters}
@@ -970,7 +968,6 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
hideLinkAnchors={true}
layout_fitWidth={returnTrue}
scriptContext={this}
- canEmbedOnDrag={true}
/>
</div>
);
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 4f3503751..e8e606030 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -53,13 +53,13 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
onPointerEnter = (e: any) => {
- if (!SnappingManager.GetIsDragging()) return;
- document.removeEventListener('pointermove', this.onPointerMove);
- document.addEventListener('pointermove', this.onPointerMove);
+ if (SnappingManager.GetIsDragging() && this.props.isContentActive()) {
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointermove', this.onPointerMove);
+ }
};
onPointerMove = (e: any) => {
- if (!SnappingManager.GetIsDragging()) return;
const dragIsRow = DragManager.docsBeingDragged.some(doc => doc.embedContainer === this.schemaDoc); // this.schemaView?._selectedDocs.has(doc) ?? false;
if (this._ref && dragIsRow) {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 97264508c..1c9c0de53 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -76,7 +76,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
isSelected: returnFalse,
setHeight: returnFalse,
select: emptyFunction,
- dropAction: 'embed',
+ dragAction: 'move',
bringToFront: emptyFunction,
renderDepth: 1,
isContentActive: returnFalse,
@@ -272,7 +272,7 @@ export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
fieldProps.isContentActive = this.selectedFunc;
return (
<div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
- {this.selected ? <FormattedTextBox allowScroll={true} {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ {this.selected ? <FormattedTextBox allowScroll={true} {...fieldProps} DataDoc={this.props.Document} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
</div>
);
}
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 422dae15b..7b2ac5713 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -36,8 +36,8 @@ $icon-size: 28px;
// fonts
$sans-serif: 'Roboto', sans-serif;
$large-header: 16px;
-$body-text: 12px;
-$small-text: 9px;
+$body-text: 13px;
+$small-text: 10px;
// $sans-serif: "Roboto Slab", sans-serif;
// misc values
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
new file mode 100644
index 000000000..256377758
--- /dev/null
+++ b/src/client/views/global/globalScripts.ts
@@ -0,0 +1,416 @@
+import { Colors } from 'browndash-components';
+import { runInAction, action } from 'mobx';
+import { aggregateBounds } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
+import { Width, Height } from '../../../fields/DocSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { undoable, UndoManager } from '../../util/UndoManager';
+import { GestureOverlay } from '../GestureOverlay';
+import { InkTranscription } from '../InkTranscription';
+import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from '../InkingStroke';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { WebBox } from '../nodes/WebBox';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { DocumentType } from '../../documents/DocumentTypes';
+
+ScriptingGlobals.add(function IsNoneSelected() {
+ return SelectionManager.Views().length <= 0;
+}, 'are no document selected');
+
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function setView(view: string) {
+ const selected = SelectionManager.Docs().lastElement();
+ selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
+});
+
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
+ const selectedViews = SelectionManager.Views();
+ if (Doc.ActiveTool !== InkTool.None) {
+ if (checkResult) {
+ return ActiveFillColor();
+ }
+ SetActiveFillColor(color ?? 'transparent');
+ } else if (selectedViews.length) {
+ if (checkResult) {
+ const selView = selectedViews.lastElement();
+ const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+ return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent';
+ }
+ selectedViews.forEach(dv => {
+ const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+ if (contentFrameNumber !== undefined) {
+ CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color });
+ } else {
+ dv.rootDoc['_' + fieldKey] = color;
+ }
+ });
+ } else {
+ const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : [];
+ if (checkResult) {
+ return selected.lastElement()?._backgroundColor ?? 'transparent';
+ }
+ selected.forEach(doc => (doc._backgroundColor = color));
+ }
+});
+
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
+ if (checkResult) {
+ return Doc.SharingDoc().headingColor;
+ }
+ Doc.SharingDoc().headingColor = undefined;
+ Doc.GetProto(Doc.SharingDoc()).headingColor = color;
+ Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
+});
+
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
+ const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ if (checkResult) {
+ if (NumCast(selected?.Document.z) >= 1) return true;
+ return false;
+ }
+ selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
+});
+
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ // prettier-ignore
+ const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+ ['grid', {
+ checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false),
+ setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
+ }],
+ ['snaplines', {
+ checkResult: (doc:Doc) => BoolCast(doc._freeform_snapLines, false),
+ setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines,
+ }],
+ ['viewAll', {
+ checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false),
+ setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
+ }],
+ ['clusters', {
+ waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
+ checkResult: (doc:Doc) => BoolCast(doc._freeform_useClusters, false),
+ setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters,
+ }],
+ ['arrange', {
+ waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
+ checkResult: (doc:Doc) => BoolCast(doc._autoArrange, false),
+ setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange,
+ }],
+ ['flashcards', {
+ checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
+ setDoc: (doc:Doc) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
+ }],
+ ]);
+
+ if (checkResult) {
+ return map.get(attr)?.checkResult(selected);
+ }
+ const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
+ SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv));
+ setTimeout(() => batch.end(), 100);
+});
+
+ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: any, checkResult?: boolean) {
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ const selected = SelectionManager.Docs().lastElement();
+ // prettier-ignore
+ const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
+ ['font', {
+ checkResult: () => RichTextMenu.Instance?.fontFamily,
+ setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
+ }],
+ ['highlight', {
+ checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
+ setDoc: () => value && RichTextMenu.Instance.setHighlight(value),
+ }],
+ ['fontColor', {
+ checkResult: () => RichTextMenu.Instance?.fontColor,
+ setDoc: () => value && RichTextMenu.Instance.setColor(value),
+ }],
+ ['alignment', {
+ checkResult: () => RichTextMenu.Instance.textAlign,
+ setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+ }],
+ ['fontSize', {
+ checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
+ setDoc: () => {
+ if (typeof value === 'number') value = value.toString();
+ if (value && Number(value).toString() === value) value += 'px';
+ RichTextMenu.Instance.setFontSize(value);
+ },
+ }],
+ ]);
+
+ if (checkResult) {
+ return map.get(attr)?.checkResult();
+ }
+ map.get(attr)?.setDoc?.();
+});
+
+type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal';
+type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
+
+ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
+ const textView = RichTextMenu.Instance?.TextView;
+ const editorView = textView?.EditorView;
+ // prettier-ignore
+ const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) =>
+ [ where, { checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false),
+ toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]);
+ // prettier-ignore
+ const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
+ [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
+ toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
+ // prettier-ignore
+ const attrs:attrfuncs[] = [
+ ['dictation', { checkResult: () => textView?._recording ? true:false,
+ toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }],
+ ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
+ toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
+ ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
+ ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
+ ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+
+ const map = new Map(attrs.concat(alignments).concat(listings));
+ if (checkResult) {
+ return map.get(charStyle)?.checkResult();
+ }
+ undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)();
+});
+
+export function checkInksToGroup() {
+ 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[Width]();
+ const height = d[Height]();
+ 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[Height]();
+ newCollection.width = newCollection[Width]();
+ }
+
+ // 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();
+}
+
+function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
+ InkTranscription.Instance?.createInkGroup();
+ if (checkResult) {
+ return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool
+ ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures)
+ ? true
+ : true
+ : false;
+ }
+ runInAction(() => {
+ if (GestureOverlay.Instance) {
+ GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
+ }
+ if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
+ if (GestureOverlay.Instance.InkShape === tool && !keepPrim) {
+ Doc.ActiveTool = InkTool.None;
+ GestureOverlay.Instance.InkShape = undefined;
+ } else {
+ Doc.ActiveTool = InkTool.Pen;
+ GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
+ }
+ } else if (tool) {
+ // pen or eraser
+ if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) {
+ Doc.ActiveTool = InkTool.None;
+ } else {
+ Doc.ActiveTool = tool as any;
+ GestureOverlay.Instance.InkShape = undefined;
+ }
+ } else {
+ Doc.ActiveTool = InkTool.None;
+ }
+ });
+}
+
+ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
+
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ // prettier-ignore
+ const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
+ ['inkMask', {
+ checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask())),
+ setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask),
+ setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
+ }],
+ ['fillColor', {
+ checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"),
+ setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
+ setMode: () => SetActiveFillColor(StrCast(value)),
+ }],
+ [ 'strokeWidth', {
+ checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()),
+ setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)),
+ setMode: () => SetActiveInkWidth(value.toString()),
+ }],
+ ['strokeColor', {
+ checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
+ setInk: (doc: Doc) => (doc.color = String(value)),
+ setMode: () => SetActiveInkColor(StrCast(value)),
+ }],
+ ]);
+
+ if (checkResult) {
+ return map.get(option)?.checkResult();
+ }
+ map.get(option)?.setMode();
+ SelectionManager.Docs()
+ .filter(doc => doc.type === DocumentType.INK)
+ .map(doc => map.get(option)?.setInk(doc));
+});
+
+/** WEB
+ * webSetURL
+ **/
+ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
+ const selected = SelectionManager.Views().lastElement();
+ if (selected?.rootDoc.type === DocumentType.WEB) {
+ if (checkResult) {
+ return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
+ }
+ selected.ComponentView?.setData?.(url);
+ //selected.rootDoc.data = new WebField(url);
+ }
+});
+ScriptingGlobals.add(function webForward(checkResult?: boolean) {
+ const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
+ if (checkResult) {
+ return selected?.forward(checkResult) ? undefined : 'lightGray';
+ }
+ selected?.forward();
+});
+ScriptingGlobals.add(function webBack(checkResult?: boolean) {
+ const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
+ if (checkResult) {
+ return selected?.back(checkResult) ? undefined : 'lightGray';
+ }
+ selected?.back();
+});
+
+/** Schema
+ * toggleSchemaPreview
+ **/
+ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (checkResult && selected) {
+ const result: boolean = NumCast(selected.schema_previewWidth) > 0;
+ if (result) return Colors.MEDIUM_BLUE;
+ else return 'transparent';
+ } else if (selected) {
+ if (NumCast(selected.schema_previewWidth) > 0) {
+ selected.schema_previewWidth = 0;
+ } else {
+ selected.schema_previewWidth = 200;
+ }
+ }
+});
+ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (checkResult && selected) {
+ return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent';
+ }
+ if (selected) {
+ selected._schema_singleLine = !selected._schema_singleLine;
+ }
+});
+
+/** STACK
+ * groupBy
+ */
+ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
+ SelectionManager.Docs().map(doc => (doc._text_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;
+});
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 5af05e491..a81707ea4 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -78,13 +78,13 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
e,
e => {
const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'embed');
- dragData.removeDropProperties = ['hidden'];
+ dragData.dropPropertiesToRemove = ['hidden'];
DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
return true;
},
emptyFunction,
action(() => {
- const trail = DocCast(this.props.docView.rootDoc.presTrail);
+ const trail = DocCast(this.props.docView.rootDoc.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
@@ -170,7 +170,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
linkSrc: this.props.sourceDoc,
linkDoc: this.props.linkDoc,
showHeader: false,
- location: [this._drag.current?.getBoundingClientRect().right ?? 100, this._drag.current?.getBoundingClientRect().top ?? e.clientY],
+ location: [(this._drag.current?.getBoundingClientRect().left ?? 100) + 40, (this._drag.current?.getBoundingClientRect().top ?? e.clientY) + 25],
noPreview: false,
})
}>
diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss
index b20ad9476..4bfb4b0b9 100644
--- a/src/client/views/linking/LinkPopup.scss
+++ b/src/client/views/linking/LinkPopup.scss
@@ -4,7 +4,6 @@
top: 0;
height: 200px;
width: 200px;
- position: absolute;
// padding: 15px;
border-radius: 3px;
@@ -37,8 +36,4 @@
margin: auto;
}
}
-
- .searchBox-container {
- background: pink;
- }
}
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 664a0fa4f..6895c0746 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -14,7 +14,6 @@ import React = require('react');
import { OpenWhere } from '../nodes/DocumentView';
interface LinkPopupProps {
- showPopup: boolean;
linkFrom?: () => Doc | undefined;
linkCreateAnchor?: () => Doc | undefined;
linkCreated?: (link: Doc) => void;
@@ -44,10 +43,9 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
getPHeight = () => 500;
render() {
- const popupVisibility = this.props.showPopup ? 'block' : 'none';
const linkDoc = this.props.linkFrom ? this.props.linkFrom : undefined;
return (
- <div className="linkPopup-container" style={{ display: popupVisibility }}>
+ <div className="linkPopup-container">
{/* <div className="linkPopup-url-container">
<input autoComplete="off" type="text" value={this.linkURL} placeholder="Enter URL..." onChange={this.onLinkChange} />
<button onPointerDown={e => this.makeLinkToURL(this.linkURL, "add:right")}
@@ -70,7 +68,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
linkSearch={true}
linkCreated={this.props.linkCreated}
fieldKey="data"
- dropAction="move"
isSelected={returnTrue}
isContentActive={returnTrue}
select={returnTrue}
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss
new file mode 100644
index 000000000..74fbfbb2c
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss
@@ -0,0 +1,15 @@
+@import '../NewLightboxStyles.scss';
+
+.newLightboxButtonMeny-container {
+ width: 100vw;
+ height: 100vh;
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
new file mode 100644
index 000000000..ff17e5c12
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -0,0 +1,50 @@
+import './ButtonMenu.scss';
+import * as React from 'react';
+import { IButtonMenu } from './utils';
+import { NewLightboxView } from '../NewLightboxView';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { CollectionDockingView } from '../../collections/CollectionDockingView';
+import { OpenWhereMod } from '../../nodes/DocumentView';
+import { Doc } from '../../../../fields/Doc';
+import { InkTool } from '../../../../fields/InkField';
+import { MainView } from '../../MainView';
+import { action } from 'mobx';
+
+export const ButtonMenu = (props: IButtonMenu) => {
+ return (
+ <div className={`newLightboxButtonMenu-container`}>
+ <div
+ className="newLightboxView-navBtn"
+ title="toggle fit width"
+ onClick={e => {
+ e.stopPropagation();
+ NewLightboxView.LightboxDoc!._fitWidth = !NewLightboxView.LightboxDoc!._fitWidth;
+ }}></div>
+ <div
+ className="newLightboxView-tabBtn"
+ title="open in tab"
+ onClick={e => {
+ e.stopPropagation();
+ CollectionDockingView.AddSplit(NewLightboxView.LightboxDoc || NewLightboxView.LightboxDoc!, OpenWhereMod.none);
+ SelectionManager.DeselectAll();
+ NewLightboxView.SetNewLightboxDoc(undefined);
+ }}></div>
+ <div
+ className="newLightboxView-penBtn"
+ title="toggle pen annotation"
+ style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }}
+ onClick={e => {
+ e.stopPropagation();
+ Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+ }}></div>
+ <div
+ className="newLightboxView-exploreBtn"
+ title="toggle explore mode to navigate among documents only"
+ style={{ background: MainView.Instance._exploreMode ? 'white' : undefined }}
+ onClick={action(e => {
+ e.stopPropagation();
+ MainView.Instance._exploreMode = !MainView.Instance._exploreMode;
+ })}></div>
+ </div>
+ );
+};
diff --git a/src/client/views/newlightbox/ButtonMenu/index.ts b/src/client/views/newlightbox/ButtonMenu/index.ts
new file mode 100644
index 000000000..f53a8c729
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/index.ts
@@ -0,0 +1 @@
+export * from './ButtonMenu' \ No newline at end of file
diff --git a/src/client/views/newlightbox/ButtonMenu/utils.ts b/src/client/views/newlightbox/ButtonMenu/utils.ts
new file mode 100644
index 000000000..096ea87ad
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/utils.ts
@@ -0,0 +1,3 @@
+export interface IButtonMenu {
+
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.scss b/src/client/views/newlightbox/ExploreView/ExploreView.scss
new file mode 100644
index 000000000..5a8ab2f87
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/ExploreView.scss
@@ -0,0 +1,44 @@
+@import '../NewLightboxStyles.scss';
+
+.exploreView-container {
+ width: 100%;
+ height: 100%;
+ border-radius: 20px;
+ position: relative;
+ // transform: scale(1);
+ background: $gray-l1;
+ border-top: $standard-border;
+ border-color: $gray-l2;
+ border-radius: 0px 0px 20px 20px;
+ transform-origin: 50% 50%;
+ overflow: hidden;
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $gray-l1;
+ }
+
+ .exploreView-doc {
+ width: 60px;
+ height: 80px;
+ position: absolute;
+ background: $blue-l2;
+ // opacity: 0.8;
+ transform-origin: 50% 50%;
+ transform: translate(-50%, -50%) scale(1);
+ cursor: pointer;
+ transition: 0.2s ease;
+ overflow: hidden;
+ font-size: 9px;
+ padding: 10px;
+ border-radius: 5px;
+
+ &:hover {
+ transform: translate(calc(-50% * 1.125), calc(-50% * 1.125)) scale(1.5);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.tsx b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
new file mode 100644
index 000000000..a1d6375c4
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
@@ -0,0 +1,32 @@
+import './ExploreView.scss';
+import { IBounds, IExploreView, emptyBounds } from './utils';
+import { IRecommendation } from '../components';
+import * as React from 'react';
+import { NewLightboxView } from '../NewLightboxView';
+import { StrCast } from '../../../../fields/Types';
+
+export const ExploreView = (props: IExploreView) => {
+ const { recs, bounds = emptyBounds } = props;
+
+ return (
+ <div className={`exploreView-container`}>
+ {recs &&
+ recs.map(rec => {
+ const x_bound: number = Math.max(Math.abs(bounds.max_x), Math.abs(bounds.min_x));
+ const y_bound: number = Math.max(Math.abs(bounds.max_y), Math.abs(bounds.min_y));
+ if (rec.embedding) {
+ const x = (rec.embedding.x / x_bound) * 50;
+ const y = (rec.embedding.y / y_bound) * 50;
+ return (
+ <div className={`exploreView-doc`} onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}>
+ {rec.title}
+ </div>
+ );
+ } else return null;
+ })}
+ <div className={`exploreView-doc`} style={{ top: `calc(50% + ${0}%)`, left: `calc(50% + ${0}%)`, background: '#073763', color: 'white' }}>
+ {StrCast(NewLightboxView.LightboxDoc?.title)}
+ </div>
+ </div>
+ );
+};
diff --git a/src/client/views/newlightbox/ExploreView/index.ts b/src/client/views/newlightbox/ExploreView/index.ts
new file mode 100644
index 000000000..bf94eedcd
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/index.ts
@@ -0,0 +1 @@
+export * from './ExploreView' \ No newline at end of file
diff --git a/src/client/views/newlightbox/ExploreView/utils.ts b/src/client/views/newlightbox/ExploreView/utils.ts
new file mode 100644
index 000000000..7d9cf226d
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/utils.ts
@@ -0,0 +1,20 @@
+import { IRecommendation } from "../components";
+
+export interface IExploreView {
+ recs?: IRecommendation[],
+ bounds?: IBounds
+}
+
+export const emptyBounds = {
+ max_x: 0,
+ max_y: 0,
+ min_x: 0,
+ min_y: 0
+}
+
+export interface IBounds {
+ max_x: number,
+ max_y: number,
+ min_x: number,
+ min_y: number
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.scss b/src/client/views/newlightbox/Header/LightboxHeader.scss
new file mode 100644
index 000000000..a9e60ea98
--- /dev/null
+++ b/src/client/views/newlightbox/Header/LightboxHeader.scss
@@ -0,0 +1,71 @@
+@import '../NewLightboxStyles.scss';
+
+.newLightboxHeader-container {
+ width: 100%;
+ height: 100%;
+ background: $gray-l1;
+ border-radius: 20px 20px 0px 0px;
+ padding: 20px;
+ display: grid;
+ grid-template-columns: 70% 30%;
+ grid-template-rows: 50% 50%;
+
+ .title-container,
+ .type-container {
+ display: flex;
+ flex-direction: row;
+ gap: 5px;
+ justify-content: flex-start;
+ align-items: center;
+ }
+
+ .title-container {
+ grid-column: 1;
+ grid-row: 1;
+ }
+
+ .type-container {
+ grid-column: 1;
+ grid-row: 2;
+ .type {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ }
+ }
+
+ .lb-label {
+ color: $gray-l3;
+ font-weight: $h1-weight;
+ }
+
+ .lb-button {
+ border: solid 1.5px black;
+ padding: 3px 5px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ align-items: center;
+ transition: 0.2s ease;
+ gap: 5px;
+ font-size: $body-size;
+ height: fit-content;
+
+ &:hover {
+ background: $gray-l2;
+ }
+
+ &.true {
+ background: $blue-l1;
+ }
+ }
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.tsx b/src/client/views/newlightbox/Header/LightboxHeader.tsx
new file mode 100644
index 000000000..a272ce294
--- /dev/null
+++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx
@@ -0,0 +1,62 @@
+import './LightboxHeader.scss';
+import * as React from 'react';
+import { INewLightboxHeader } from "./utils";
+import { NewLightboxView } from '../NewLightboxView';
+import { StrCast } from '../../../../fields/Types';
+import { EditableText } from '../components/EditableText';
+import { getType } from '../utils';
+import { Button, IconButton, Size, Type } from 'browndash-components';
+import { MdExplore, MdTravelExplore } from 'react-icons/md'
+import { BsBookmark, BsBookmarkFill } from 'react-icons/bs'
+import { Doc } from '../../../../fields/Doc';
+import { LightboxView } from '../../LightboxView';
+import { Colors } from '../../global/globalEnums';
+
+
+export const NewLightboxHeader = (props: INewLightboxHeader) => {
+ const {height = 100, width} = props;
+ const [doc, setDoc] = React.useState<Doc | undefined>(LightboxView.LightboxDoc)
+ const [editing, setEditing] = React.useState<boolean>(false)
+ const [title, setTitle] = React.useState<JSX.Element | null>(
+ (null)
+ )
+ React.useEffect(() => {
+ let lbDoc = LightboxView.LightboxDoc
+ setDoc(lbDoc)
+ if (lbDoc) {
+ setTitle(
+ <EditableText
+ editing={editing}
+ text={StrCast(lbDoc.title)}
+ onEdit={(newText: string) => {
+ if(lbDoc) lbDoc.title = newText;
+ }}
+ setEditing={setEditing}
+ />)
+ }
+ }, [LightboxView.LightboxDoc])
+
+ const [saved, setSaved] = React.useState<boolean>(false)
+
+ if (!doc) return null
+ else return <div className={`newLightboxHeader-container`} onPointerDown={(e) => e.stopPropagation()} style={{ minHeight: height, height: height, width: width }}>
+ <div className={`title-container`}>
+ <div className={`lb-label`}>Title</div>
+ {title}
+ </div>
+ <div className={`type-container`}>
+ <div className={`lb-label`}>Type</div>
+ <div className={`type`}>{getType(StrCast(doc.type))}</div>
+ </div>
+ <div style={{gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
+ </div>
+ <div style={{gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
+ <Button onClick={() => {
+ console.log(NewLightboxView.ExploreMode)
+ NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode)
+ }} size={Size.XSMALL} color={Colors.DARK_GRAY} type={Type.SEC} text={"t-SNE 2D Embeddings"} icon={<MdTravelExplore/>}/>
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/index.ts b/src/client/views/newlightbox/Header/index.ts
new file mode 100644
index 000000000..090677c16
--- /dev/null
+++ b/src/client/views/newlightbox/Header/index.ts
@@ -0,0 +1 @@
+export * from './LightboxHeader' \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/utils.ts b/src/client/views/newlightbox/Header/utils.ts
new file mode 100644
index 000000000..22e0487c2
--- /dev/null
+++ b/src/client/views/newlightbox/Header/utils.ts
@@ -0,0 +1,4 @@
+export interface INewLightboxHeader {
+ height?: number
+ width?: number
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/NewLightboxStyles.scss b/src/client/views/newlightbox/NewLightboxStyles.scss
new file mode 100644
index 000000000..ff4a6c971
--- /dev/null
+++ b/src/client/views/newlightbox/NewLightboxStyles.scss
@@ -0,0 +1,73 @@
+$white: white;
+$black: black;
+
+// gray
+$gray-l1: rgba(230, 230, 230, 1);
+$gray-l2: rgb(201, 201, 201);
+$gray-l3: rgba(87, 87, 87, 1);
+
+// blue
+$blue-l1: #cfe2f3;
+$blue-l2: #6fa8dc;
+$blue-l3: #0b5394;
+$blue-l4: #073763;
+
+// view backgrounds
+$background-dm: black;
+$background-lm: white;
+$header-dm: $gray-l3;
+$header-lm: $gray-l1;
+
+// border
+$standard-border: solid 2px;
+
+// standard shadow
+
+
+$text-color-dm: $gray-l1;
+$text-color-lm: $gray-l3;
+
+
+// text / font
+$title-size: 2rem;
+$title-weight: 700;
+
+$h1-size: 15px;
+$h1-weight: 700;
+
+$h2-size: 13px;
+$h2-weight: 600;
+
+
+$body-size: 10px;
+$body-weight: 400;
+
+// header
+$header-height: 40px;
+
+@keyframes skeleton-loading-l3 {
+ 0% {
+ background-color: rgba(128, 128, 128, 1);
+ }
+ 100% {
+ background-color: rgba(128, 128, 128, 0.5);
+ }
+}
+
+@keyframes skeleton-loading-l2 {
+ 0% {
+ background-color: rgba(182, 182, 182, 1);
+ }
+ 100% {
+ background-color: rgba(182, 182, 182, 0.5);
+ }
+}
+
+@keyframes skeleton-loading-l1 {
+ 0% {
+ background-color: rgba(230, 230, 230, 1);
+ }
+ 100% {
+ background-color: rgba(230, 230, 230, 0.5);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/NewLightboxView.scss b/src/client/views/newlightbox/NewLightboxView.scss
new file mode 100644
index 000000000..76c34bcf9
--- /dev/null
+++ b/src/client/views/newlightbox/NewLightboxView.scss
@@ -0,0 +1,34 @@
+@import './NewLightboxStyles.scss';
+
+.newLightboxView-frame {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #474545bb;
+ backdrop-filter: blur(4px);
+ z-index: 1000;
+
+ .app-document {
+ width: 100%;
+ height: 100%;
+ display: grid;
+ }
+
+ .explore {
+ width: 100%;
+ height: 100%;
+ display: grid;
+ }
+
+ .newLightboxView-contents {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ .newLightboxView-doc {
+ position: relative;
+ }
+ }
+}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
new file mode 100644
index 000000000..3acbd1a32
--- /dev/null
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -0,0 +1,388 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils';
+import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { DocUtils } from '../../documents/Documents';
+import { DocumentManager } from '../../util/DocumentManager';
+import { LinkManager } from '../../util/LinkManager';
+import { SelectionManager } from '../../util/SelectionManager';
+import { Transform } from '../../util/Transform';
+import { GestureOverlay } from '../GestureOverlay';
+import { MainView } from '../MainView';
+import { DefaultStyleProvider } from '../StyleProvider';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { TabDocView } from '../collections/TabDocView';
+import { DocumentView, OpenWhere } from '../nodes/DocumentView';
+import { ExploreView } from './ExploreView';
+import { IBounds, emptyBounds } from './ExploreView/utils';
+import { NewLightboxHeader } from './Header';
+import './NewLightboxView.scss';
+import { RecommendationList } from './RecommendationList';
+import { IRecommendation } from './components';
+import { fetchKeywords, fetchRecommendations } from './utils';
+import { List } from '../../../fields/List';
+import { LightboxView } from '../LightboxView';
+
+enum LightboxStatus {
+ RECOMMENDATIONS = "recommendations",
+ ANNOTATIONS = "annotations",
+ NONE = "none"
+}
+
+interface LightboxViewProps {
+ PanelWidth: number;
+ PanelHeight: number;
+ maxBorder: number[];
+}
+
+type LightboxSavedState = {
+ panX: Opt<number>;
+ panY: Opt<number>;
+ scale: Opt<number>;
+ scrollTop: Opt<number>;
+ layout_fieldKey: Opt<string>;
+};
+@observer
+export class NewLightboxView extends React.Component<LightboxViewProps> {
+ @computed public static get LightboxDoc() {
+ return this._doc;
+ }
+ private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate;
+ @observable private static _layoutTemplate: Opt<Doc>;
+ @observable private static _layoutTemplateString: Opt<string>;
+ @observable private static _doc: Opt<Doc>;
+ @observable private static _docTarget: Opt<Doc>;
+ @observable private static _docFilters: string[] = []; // filters
+ private static _savedState: Opt<LightboxSavedState>;
+ private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
+ @observable private static _future: Opt<Doc[]> = [];
+ @observable private static _docView: Opt<DocumentView>;
+
+ // keywords
+ @observable private static _keywords: string[] = []
+ @action public static SetKeywords(kw: string[]) {
+ this._keywords = kw
+ }
+ @computed public static get Keywords() {
+ return this._keywords
+ }
+
+ // query
+ @observable private static _query: string = ''
+ @action public static SetQuery(query: string) {
+ this._query = query
+ }
+ @computed public static get Query() {
+ return this._query
+ }
+
+ // keywords
+ @observable private static _recs: IRecommendation[] = []
+ @action public static SetRecs(recs: IRecommendation[]) {
+ this._recs = recs
+ }
+ @computed public static get Recs() {
+ return this._recs
+ }
+
+ // bounds
+ @observable private static _bounds: IBounds = emptyBounds;
+ @action public static SetBounds(bounds: IBounds) {
+ this._bounds = bounds;
+ }
+ @computed public static get Bounds() {
+ return this._bounds;
+ }
+
+ // explore
+ @observable private static _explore: Opt<boolean> = false;
+ @action public static SetExploreMode(status: Opt<boolean>) {
+ this._explore = status;
+ }
+ @computed public static get ExploreMode() {
+ return this._explore;
+ }
+
+ // newLightbox sidebar status
+ @observable private static _sidebarStatus: Opt<string> = "";
+ @action public static SetSidebarStatus(sidebarStatus: Opt<string>) {
+ this._sidebarStatus = sidebarStatus;
+ }
+ @computed public static get SidebarStatus() {
+ return this._sidebarStatus;
+ }
+
+ static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<LightboxSavedState> }[] = [];
+ @action public static SetNewLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
+ if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
+ if (this._savedState.panX !== undefined) this.LightboxDoc._freeform_panX = this._savedState.panX;
+ if (this._savedState.panY !== undefined) this.LightboxDoc._freeform_panY = this._savedState.panY;
+ if (this._savedState.scrollTop !== undefined) this.LightboxDoc._layout_scrollTop = this._savedState.scrollTop;
+ if (this._savedState.scale !== undefined) this.LightboxDoc._freeform_scale = this._savedState.scale;
+ this.LightboxDoc.layout_fieldKey = this._savedState.layout_fieldKey;
+ }
+ if (!doc) {
+ this._docFilters && (this._docFilters.length = 0);
+ this._future = this._history = [];
+ Doc.ActiveTool = InkTool.None;
+ MainView.Instance._exploreMode = false;
+ } else {
+ const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+ l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+ CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
+ //TabDocView.PinDoc(doc, { hidePresBox: true });
+ this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
+ if (doc !== LightboxView.LightboxDoc) {
+ this._savedState = {
+ layout_fieldKey: StrCast(doc.layout_fieldKey),
+ panX: Cast(doc.freeform_panX, 'number', null),
+ panY: Cast(doc.freeform_panY, 'number', null),
+ scale: Cast(doc.freeform_scale, 'number', null),
+ scrollTop: Cast(doc.layout_scrollTop, 'number', null),
+ };
+ }
+ }
+ if (future) {
+ this._future = [
+ ...(this._future ?? []),
+ ...(this.LightboxDoc ? [this.LightboxDoc] : []),
+ ...future
+ .slice()
+ .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
+ .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length),
+ ];
+ }
+ this._doc = doc;
+ this._layoutTemplate = layoutTemplate instanceof Doc ? layoutTemplate : undefined;
+ if (doc && (typeof layoutTemplate === 'string' ? layoutTemplate : undefined)) {
+ doc.layout_fieldKey = layoutTemplate;
+ }
+ this._docTarget = target || doc;
+
+ return true;
+ }
+ public static IsNewLightboxDocView(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]);
+ }
+ newLightboxWidth = () => this.props.PanelWidth - 420;
+ newLightboxHeight = () => this.props.PanelHeight - 140;
+ newLightboxScreenToLocal = () => 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="newLightboxView-navBtn-frame"
+ style={{
+ display: display(),
+ left,
+ width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+ bottom,
+ }}>
+ <div className="newLightboxView-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>
+ );
+ };
+ public static GetSavedState(doc: Doc) {
+ return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined;
+ }
+
+ // adds a cookie to the newLightbox view - the cookie becomes part of a filter which will display any documents whose cookie metadata field matches this cookie
+ @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`);
+ }
+ }
+ public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => {
+ SelectionManager.DeselectAll();
+ return NewLightboxView.SetNewLightboxDoc(
+ doc,
+ undefined,
+ [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
+ (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
+ ),
+ layoutTemplate
+ );
+ };
+ docFilters = () => NewLightboxView._docFilters || [];
+ addDocTab = NewLightboxView.AddDocTab;
+ @action public static Next() {
+ const doc = NewLightboxView._doc!;
+ const target = (NewLightboxView._docTarget = this._future?.pop());
+ const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+ if (targetDocView && target) {
+ const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
+ l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+ DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target });
+ } else {
+ if (!target && NewLightboxView.path.length) {
+ const saved = NewLightboxView._savedState;
+ if (LightboxView.LightboxDoc && saved) {
+ LightboxView.LightboxDoc._freeform_panX = saved.panX;
+ LightboxView.LightboxDoc._freeform_panY = saved.panY;
+ LightboxView.LightboxDoc._freeform_scale = saved.scale;
+ LightboxView.LightboxDoc._layout_scrollTop = saved.scrollTop;
+ }
+ const pop = NewLightboxView.path.pop();
+ if (pop) {
+ NewLightboxView._doc = pop.doc;
+ NewLightboxView._docTarget = pop.target;
+ NewLightboxView._future = pop.future;
+ NewLightboxView._history = pop.history;
+ NewLightboxView._savedState = pop.saved;
+ }
+ } else {
+ NewLightboxView.SetNewLightboxDoc(target);
+ }
+ }
+ }
+
+ @action public static Previous() {
+ const previous = NewLightboxView._history?.pop();
+ if (!previous || !NewLightboxView._history?.length) {
+ NewLightboxView.SetNewLightboxDoc(undefined);
+ return;
+ }
+ const { doc, target } = NewLightboxView._history?.lastElement();
+ const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
+ if (docView) {
+ NewLightboxView._docTarget = target;
+ target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ } else {
+ NewLightboxView.SetNewLightboxDoc(doc, target);
+ }
+ if (NewLightboxView._future?.lastElement() !== previous.target || previous.doc) NewLightboxView._future?.push(previous.target || previous.doc);
+ }
+ @action
+ stepInto = () => {
+ NewLightboxView.path.push({
+ doc: LightboxView.LightboxDoc,
+ target: NewLightboxView._docTarget,
+ future: NewLightboxView._future,
+ history: NewLightboxView._history,
+ saved: NewLightboxView._savedState,
+ });
+ const coll = NewLightboxView._docTarget;
+ if (coll) {
+ const fieldKey = Doc.LayoutFieldKey(coll);
+ const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '_annotations'])];
+ const links = LinkManager.Links(coll)
+ .map(link => LinkManager.getOppositeAnchor(link, coll))
+ .filter(doc => doc)
+ .map(doc => doc!);
+ NewLightboxView.SetNewLightboxDoc(coll, undefined, contents.length ? contents : links);
+ }
+ };
+
+ @computed
+ get documentView() {
+ if (!LightboxView.LightboxDoc) return null
+ else return (<GestureOverlay isActive={true}>
+ <DocumentView
+ ref={action((r: DocumentView | null) => (NewLightboxView._docView = r !== null ? r : undefined))}
+ Document={LightboxView.LightboxDoc}
+ DataDoc={undefined}
+ PanelWidth={this.newLightboxWidth}
+ PanelHeight={this.newLightboxHeight}
+ LayoutTemplate={NewLightboxView.LightboxDocTemplate}
+ isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
+ isContentActive={returnTrue}
+ styleProvider={DefaultStyleProvider}
+ ScreenToLocalTransform={this.newLightboxScreenToLocal}
+ renderDepth={0}
+ rootSelected={returnTrue}
+ docViewPath={returnEmptyDoclist}
+ childFilters={this.docFilters}
+ childFiltersByRanges={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ addDocument={undefined}
+ removeDocument={undefined}
+ whenChildContentsActiveChanged={emptyFunction}
+ addDocTab={this.addDocTab}
+ pinToPres={TabDocView.PinDoc}
+ bringToFront={emptyFunction}
+ onBrowseClick={MainView.Instance.exploreMode}
+ focus={emptyFunction}
+ />
+ </GestureOverlay>)
+ }
+
+ future = () => NewLightboxView._future;
+ render() {
+ let newLightboxHeaderHeight = 100;
+ let downx = 0,
+ downy = 0;
+ return !LightboxView.LightboxDoc ? null : (
+ <div
+ className="newLightboxView-frame"
+ onPointerDown={e => {
+ downx = e.clientX;
+ downy = e.clientY;
+ }}
+ onClick={e => {
+ if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
+ NewLightboxView.SetNewLightboxDoc(undefined);
+ }
+ }}>
+ <div className={`app-document`} style={{gridTemplateColumns: `calc(100% - 400px) 400px`}}>
+ <div
+ className="newLightboxView-contents"
+ style={{
+ top: 20,
+ left: 20,
+ width: this.newLightboxWidth(),
+ height: this.newLightboxHeight() - 40,
+ }}>
+ <NewLightboxHeader height={newLightboxHeaderHeight} width={this.newLightboxWidth()} />
+ {!NewLightboxView._explore ?
+ <div className="newLightboxView-doc" style={{height: this.newLightboxHeight()}}>
+ {this.documentView}
+ </div>
+ :
+ <div className={`explore`}>
+ <ExploreView recs={NewLightboxView.Recs} bounds={NewLightboxView.Bounds}/>
+ </div>
+ }
+ </div>
+ <RecommendationList keywords={NewLightboxView.Keywords}/>
+ </div>
+
+ </div>
+ );
+ }
+}
+interface NewLightboxTourBtnProps {
+ navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element;
+ future: () => Opt<Doc[]>;
+ stepInto: () => void;
+}
+@observer
+export class NewLightboxTourBtn extends React.Component<NewLightboxTourBtnProps> {
+ render() {
+ return this.props.navBtn(
+ '50%',
+ 0,
+ 0,
+ 'chevron-down',
+ () => (LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? '' : 'none'),
+ e => {
+ e.stopPropagation();
+ this.props.stepInto();
+ },
+ ''
+ );
+ }
+}
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.scss b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
new file mode 100644
index 000000000..40dd47e47
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
@@ -0,0 +1,117 @@
+@import '../NewLightboxStyles.scss';
+
+.recommendationlist-container {
+ height: calc(100% - 40px);
+ margin: 20px;
+ border-radius: 20px;
+ overflow-y: scroll;
+
+ .recommendations {
+ height: fit-content;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ background: $gray-l1;
+ border-radius: 0px 0px 20px 20px;
+ }
+
+ .header {
+ top: 0px;
+ position: sticky;
+ background: $gray-l1;
+ border-bottom: $standard-border;
+ border-color: $gray-l2;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ border-radius: 20px 20px 0px 0px;
+ padding: 20px;
+ z-index: 2;
+ gap: 10px;
+ color: $text-color-lm;
+
+ .lb-label {
+ color: $gray-l3;
+ font-weight: $h1-weight;
+ font-size: $body-size;
+ }
+
+ .lb-caret {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 5px;
+ cursor: pointer;
+ width: 100%;
+ user-select: none;
+ font-size: $body-size;
+ }
+
+ .more {
+ width: 100%;
+ }
+
+ &.dark {
+ color: $text-color-dm;
+ }
+
+ .title {
+ height: 30px;
+ min-height: 30px;
+ font-size: $h1-size;
+ font-weight: $h1-weight;
+ text-align: left;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ }
+
+ .keywords {
+ display: flex;
+ flex-flow: row wrap;
+ gap: 5px;
+
+ .keyword-input {
+ padding: 3px 7px;
+ background: $gray-l2;
+ outline: none;
+ border: none;
+ height: 21.5px;
+ color: $text-color-lm;
+ }
+
+ .keyword {
+ padding: 3px 7px;
+ width: fit-content;
+ background: $gray-l2;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: row;
+ gap: 10px;
+ font-size: $body-size;
+ font-weight: $body-weight;
+
+ &.loading {
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ min-width: 70px;
+ height: 21.5px;
+ }
+ }
+
+ }
+ }
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $gray-l1;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
new file mode 100644
index 000000000..9f3c32e4e
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
@@ -0,0 +1,196 @@
+import { GrClose } from 'react-icons/gr';
+import { IRecommendation, Recommendation } from "../components";
+import './RecommendationList.scss';
+import * as React from 'react';
+import { IRecommendationList } from "./utils";
+import { NewLightboxView } from '../NewLightboxView';
+import { DocCast, StrCast } from '../../../../fields/Types';
+import { FaCaretDown, FaCaretUp } from 'react-icons/fa';
+import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
+import { IDocRequest, fetchKeywords, fetchRecommendations } from '../utils';
+import { IBounds } from '../ExploreView/utils';
+import { List } from '../../../../fields/List';
+import { Id } from '../../../../fields/FieldSymbols';
+import { LightboxView } from '../../LightboxView';
+import { IconButton, Size, Type } from 'browndash-components';
+import { Colors } from '../../global/globalEnums';
+
+export const RecommendationList = (props: IRecommendationList) => {
+ const {loading, keywords} = props
+ const [loadingKeywords, setLoadingKeywords] = React.useState<boolean>(true)
+ const [showMore, setShowMore] = React.useState<boolean>(false)
+ const [keywordsLoc, setKeywordsLoc] = React.useState<string[]>([])
+ const [update, setUpdate] = React.useState<boolean>(true)
+ const initialRecs: IRecommendation[] = [
+ {loading: true},
+ {loading: true},
+ {loading: true},
+ {loading: true},
+ {loading: true}
+ ];
+ const [recs, setRecs] = React.useState<IRecommendation[]>(initialRecs)
+
+ React.useEffect(() => {
+ const getKeywords = async () => {
+ let text = StrCast(LightboxView.LightboxDoc?.text)
+ console.log('[1] fetching keywords')
+ const response = await fetchKeywords(text, 5, true)
+ console.log('[2] response:', response)
+ const kw = response.keywords;
+ console.log(kw);
+ NewLightboxView.SetKeywords(kw);
+ if (LightboxView.LightboxDoc) {
+ console.log('setting keywords on doc')
+ LightboxView.LightboxDoc.keywords = new List<string>(kw);
+ setKeywordsLoc(NewLightboxView.Keywords);
+ }
+ setLoadingKeywords(false)
+ }
+ let keywordsList = StrListCast(LightboxView.LightboxDoc!.keywords)
+ if (!keywordsList || keywordsList.length < 2) {
+ setLoadingKeywords(true)
+ getKeywords()
+ setUpdate(!update)
+ } else {
+ setKeywordsLoc(keywordsList)
+ setLoadingKeywords(false)
+ setUpdate(!update)
+ }
+ }, [NewLightboxView.LightboxDoc])
+
+ // terms: vannevar bush, information spaces,
+ React.useEffect(() => {
+ const getRecommendations = async () => {
+ console.log('fetching recommendations')
+ let query = 'undefined'
+ if (keywordsLoc) query = keywordsLoc.join(',')
+ let src = StrCast(NewLightboxView.LightboxDoc?.text)
+ let dashDocs:IDocRequest[] = [];
+ // get linked docs
+ let linkedDocs = DocListCast(NewLightboxView.LightboxDoc?.links)
+ console.log("linked docs", linkedDocs)
+ // get context docs (docs that are also in the collection)
+ // let contextDocs: Doc[] = DocListCast(DocCast(LightboxView.LightboxDoc?.context).data)
+ // let docId = LightboxView.LightboxDoc && LightboxView.LightboxDoc[Id]
+ // console.log("context docs", contextDocs)
+ // contextDocs.forEach((doc: Doc) => {
+ // if (docId !== doc[Id]){
+ // dashDocs.push({
+ // title: StrCast(doc.title),
+ // text: StrCast(doc.text),
+ // id: doc[Id],
+ // type: StrCast(doc.type)
+ // })
+ // }
+ // })
+ console.log("dash docs", dashDocs)
+ if (query !== undefined) {
+ const response = await fetchRecommendations(src, query, [], true)
+ const num_recs = response.num_recommendations
+ const recs = response.recommendations
+ const keywords = response.keywords
+ const response_bounds: IBounds = {
+ max_x: response.max_x,
+ max_y: response.max_y,
+ min_x: response.min_x,
+ min_y: response.min_y
+ }
+ // if (NewLightboxView.NewLightboxDoc) {
+ // NewLightboxView.NewLightboxDoc.keywords = new List<string>(keywords);
+ // setKeywordsLoc(NewLightboxView.Keywords);
+ // }
+ // console.log(response_bounds)
+ NewLightboxView.SetBounds(response_bounds)
+ const recommendations: IRecommendation[] = [];
+ for (const key in recs) {
+ console.log(key)
+ const title = recs[key].title;
+ const url = recs[key].url
+ const type = recs[key].type
+ const text = recs[key].text
+ const transcript = recs[key].transcript
+ const previewUrl = recs[key].previewUrl
+ const embedding = recs[key].embedding
+ const distance = recs[key].distance
+ const source = recs[key].source
+ const related_concepts = recs[key].related_concepts
+ const docId = recs[key].doc_id
+ related_concepts.length >= 1 && recommendations.push({
+ title: title,
+ data: url,
+ type: type,
+ text: text,
+ transcript: transcript,
+ previewUrl: previewUrl,
+ embedding: embedding,
+ distance: Math.round(distance * 100) / 100,
+ source: source,
+ related_concepts: related_concepts,
+ docId: docId
+ })
+ }
+ recommendations.sort((a, b) => {
+ if (a.distance && b.distance) {
+ return a.distance - b.distance
+ } else return 0
+ })
+ console.log("[rec]: ", recommendations)
+ NewLightboxView.SetRecs(recommendations)
+ setRecs(recommendations)
+ }
+ }
+ getRecommendations();
+ }, [update])
+
+
+
+ return <div className={`recommendationlist-container`} onPointerDown={(e) => {e.stopPropagation()}}>
+ <div className={`header`}>
+ <div className={`title`}>
+ Recommendations
+ </div>
+ {NewLightboxView.LightboxDoc && <div style={{fontSize: 10}}>
+ The recommendations are produced based on the text in the document <b><u>{StrCast(NewLightboxView.LightboxDoc.title)}</u></b>. The following keywords are used to fetch the recommendations.
+ </div>}
+ <div className={`lb-label`}>Keywords</div>
+ {loadingKeywords ? <div className={`keywords`}>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ </div>
+ :
+ <div className={`keywords`}>
+ {keywordsLoc && keywordsLoc.map((word, ind) => {
+ return <div className={`keyword`}>
+ {word}
+ <IconButton type={Type.PRIM} size={Size.XSMALL} color={Colors.DARK_GRAY} icon={<GrClose/>} onClick={() => {
+ let kw = keywordsLoc
+ kw.splice(ind)
+ NewLightboxView.SetKeywords(kw)
+ }}/>
+ </div>
+ })}
+ </div>
+ }
+ {!showMore ?
+ <div className={`lb-caret`} onClick={() => {setShowMore(true)}}>
+ More <FaCaretDown/>
+ </div>
+ :
+ <div className={`more`}>
+ <div className={`lb-caret`} onClick={() => {setShowMore(false)}}>
+ Less <FaCaretUp/>
+ </div>
+ <div className={`lb-label`}>Type</div>
+ <div className={`lb-label`}>Sources</div>
+ </div>
+ }
+ </div>
+ <div className={`recommendations`}>
+ {recs && recs.map((rec: IRecommendation) => {
+ return <Recommendation {...rec} />
+ })}
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/index.ts b/src/client/views/newlightbox/RecommendationList/index.ts
new file mode 100644
index 000000000..f4555c1f2
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/index.ts
@@ -0,0 +1 @@
+export * from './RecommendationList' \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/utils.ts b/src/client/views/newlightbox/RecommendationList/utils.ts
new file mode 100644
index 000000000..cdfff3258
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/utils.ts
@@ -0,0 +1,9 @@
+import { IRecommendation } from "../components";
+
+export interface IRecommendationList {
+ loading?: boolean,
+ keywords?: string[],
+ recs?: IRecommendation[]
+ getRecs?: any
+}
+
diff --git a/src/client/views/newlightbox/components/EditableText/EditableText.scss b/src/client/views/newlightbox/components/EditableText/EditableText.scss
new file mode 100644
index 000000000..7828538ab
--- /dev/null
+++ b/src/client/views/newlightbox/components/EditableText/EditableText.scss
@@ -0,0 +1,34 @@
+@import '../../NewLightboxStyles.scss';
+
+.lb-editableText,
+.lb-displayText {
+ padding: 4px 7px !important;
+ border: $standard-border !important;
+ border-color: $gray-l2 !important;
+}
+
+.lb-editableText {
+ -webkit-appearance: none;
+ overflow: hidden;
+ font-size: inherit;
+ border: none;
+ outline: none;
+ width: 100%;
+ margin: 0px;
+ padding: 0px;
+ box-shadow: none !important;
+ background: none;
+
+ &:focus {
+ outline: none;
+ background-color: $blue-l1;
+ }
+}
+
+.lb-displayText {
+ cursor: text !important;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ font-size: inherit;
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/EditableText/EditableText.tsx b/src/client/views/newlightbox/components/EditableText/EditableText.tsx
new file mode 100644
index 000000000..e9e7ca264
--- /dev/null
+++ b/src/client/views/newlightbox/components/EditableText/EditableText.tsx
@@ -0,0 +1,65 @@
+import * as React from 'react'
+import './EditableText.scss'
+import { Size } from 'browndash-components'
+
+export interface IEditableTextProps {
+ text: string
+ placeholder?: string
+ editing: boolean
+ onEdit: (newText: string) => void
+ setEditing: (editing: boolean) => void
+ backgroundColor?: string
+ size?: Size
+ height?: number
+}
+
+/**
+ * Editable Text is used for inline renaming of some text.
+ * It appears as normal UI text but transforms into a text input field when the user clicks on or focuses it.
+ * @param props
+ * @returns
+ */
+export const EditableText = (props: IEditableTextProps) => {
+ const {
+ editing,
+ height,
+ size,
+ text,
+ onEdit,
+ setEditing,
+ backgroundColor,
+ placeholder,
+ } = props
+
+ const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ onEdit(event.target.value)
+ }
+
+ return editing ? (
+ <input
+ style={{ background: backgroundColor, height: height }}
+ placeholder={placeholder}
+ size={1}
+ className="lb-editableText"
+ autoFocus
+ onChange={handleOnChange}
+ onBlur={() => setEditing(false)}
+ defaultValue={text}
+ ></input>
+ ) : (
+ <input
+ style={{ background: backgroundColor, height: height }}
+ placeholder={placeholder}
+ size={1}
+ className="lb-editableText"
+ autoFocus
+ onChange={handleOnChange}
+ onBlur={() => setEditing(false)}
+ defaultValue={text}
+ ></input>
+ // <div className="lb-displayText" onClick={(e) => {
+ // e.stopPropagation()
+ // setEditing(true)
+ // }}>{text}</div>
+ )
+}
diff --git a/src/client/views/newlightbox/components/EditableText/index.ts b/src/client/views/newlightbox/components/EditableText/index.ts
new file mode 100644
index 000000000..e3367b175
--- /dev/null
+++ b/src/client/views/newlightbox/components/EditableText/index.ts
@@ -0,0 +1 @@
+export * from './EditableText'
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
new file mode 100644
index 000000000..c86c63ba0
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
@@ -0,0 +1,176 @@
+@import '../../NewLightboxStyles.scss';
+
+.recommendation-container {
+ width: 100%;
+ height: fit-content;
+ min-height: 180px;
+ border-radius: 20px;
+ display: grid;
+ grid-template-columns: 0% 100%;
+ grid-template-rows: auto auto auto auto auto;
+ gap: 5px 0px;
+ padding: 10px;
+ cursor: pointer;
+ transition: 0.2s ease;
+ border: $standard-border;
+ border-color: $gray-l2;
+ background: white;
+
+ &:hover {
+ // background: white !important;
+ transform: scale(1.02);
+ z-index: 0;
+
+ .title {
+ text-decoration: underline;
+ }
+ }
+
+ &.previewUrl {
+ grid-template-columns: calc(30% - 10px) 70%;
+ grid-template-rows: auto auto auto auto auto;
+ gap: 5px 10px;
+ }
+
+ &.loading {
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ border: none;
+ grid-template-columns: calc(30% - 10px) 70%;
+ grid-template-rows: auto auto auto auto auto;
+ gap: 5px 10px;
+
+ .image-container,
+ .title,
+ .info,
+ .source,
+ .explainer,
+ .hide-rec {
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ }
+
+ .title {
+ border-radius: 20px;
+ }
+ }
+
+ .distance-container,
+ .type-container,
+ .source-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 5px;
+ }
+
+ .image-container {
+ grid-row: 2/5;
+ grid-column: 1;
+ border-radius: 20px;
+ overflow: hidden;
+
+ .image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ .title {
+ grid-row: 1;
+ grid-column: 1/3;
+ border-radius: 20px;
+ font-size: $h2-size;
+ font-weight: $h2-weight;
+ overflow: hidden;
+ border-radius: 0px;
+ min-height: 30px;
+ }
+
+ .info {
+ grid-row: 2;
+ grid-column: 2;
+ border-radius: 20px;
+ display: flex;
+ flex-direction: row;
+ gap: 5px;
+ font-size: $body-size;
+
+ .lb-type {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ }
+ }
+
+ .lb-label {
+ color: $gray-l3;
+ font-weight: $h1-weight;
+ font-size: $body-size;
+ }
+
+ .source {
+ grid-row: 3;
+ grid-column: 2;
+ border-radius: 20px;
+ font-size: $body-size;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+
+ .lb-source {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ border-radius: 10px;
+ white-space: nowrap;
+ max-width: 130px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ }
+
+ .explainer {
+ grid-row: 4;
+ grid-column: 2;
+ border-radius: 20px;
+ font-size: 10px;
+ width: 100%;
+ background: $blue-l1;
+ border-radius: 0;
+ padding: 10px;
+
+ .concepts-container {
+ display: flex;
+ flex-flow: row wrap;
+ margin-top: 3px;
+ gap: 3px;
+ .concept {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ }
+ }
+ }
+
+ .hide-rec {
+ grid-row: 5;
+ grid-column: 2;
+ border-radius: 20px;
+ font-size: $body-size;
+ display: flex;
+ align-items: center;
+ margin-top: 5px;
+ gap: 5px;
+ justify-content: flex-end;
+ text-transform: underline;
+ }
+
+ &.dark {
+ background: $black;
+ border-color: $white;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ border-color: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
new file mode 100644
index 000000000..96846673b
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
@@ -0,0 +1,102 @@
+import * as React from 'react';
+import { IRecommendation } from './utils';
+import './Recommendation.scss';
+import { getType } from '../../utils';
+import { FaEyeSlash } from 'react-icons/fa';
+import { NewLightboxView } from '../../NewLightboxView';
+import { DocumentManager } from '../../../../util/DocumentManager';
+import { Doc } from '../../../../../fields/Doc';
+import { Docs } from '../../../../documents/Documents';
+
+export const Recommendation = (props: IRecommendation) => {
+ const { title, data, type, text, transcript, loading, source, previewUrl, related_concepts, distance, docId } = props;
+
+ return (
+ <div
+ className={`recommendation-container ${loading && 'loading'} ${previewUrl && 'previewUrl'}`}
+ onClick={() => {
+ let doc: Doc | null = null;
+ if (source == 'Dash' && docId) {
+ const docView = DocumentManager.Instance.getDocumentViewsById(docId).lastElement();
+ if (docView) {
+ doc = docView.rootDoc;
+ }
+ } else if (data) {
+ switch (type) {
+ case 'YouTube':
+ console.log('create ', type, 'document');
+ doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript });
+ break;
+ case 'Video':
+ console.log('create ', type, 'document');
+ doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript });
+ break;
+ case 'Webpage':
+ console.log('create ', type, 'document');
+ doc = Docs.Create.WebDocument(data, { title: title, text: text });
+ break;
+ case 'HTML':
+ console.log('create ', type, 'document');
+ doc = Docs.Create.WebDocument(data, { title: title, text: text });
+ break;
+ case 'Text':
+ console.log('create ', type, 'document');
+ doc = Docs.Create.TextDocument(data, { title: title, text: text });
+ break;
+ case 'PDF':
+ console.log('create ', type, 'document');
+ doc = Docs.Create.PdfDocument(data, { title: title, text: text });
+ break;
+ }
+ }
+ if (doc !== null) NewLightboxView.SetNewLightboxDoc(doc);
+ }}>
+ {loading ? <div className={`image-container`}></div> : previewUrl ? <div className={`image-container`}>{<img className={`image`} src={previewUrl}></img>}</div> : null}
+ <div className={`title`}>{title}</div>
+ <div className={`info`}>
+ {!loading && (
+ <div className={`type-container`}>
+ <div className={`lb-label`}>Type</div>
+ <div className={`lb-type`}>{getType(type!)}</div>
+ </div>
+ )}
+ {!loading && (
+ <div className={`distance-container`}>
+ <div className={`lb-label`}>Distance</div>
+ <div className={`lb-distance`}>{distance}</div>
+ </div>
+ )}
+ </div>
+ <div className={`source`}>
+ {!loading && (
+ <div className={`source-container`}>
+ <div className={`lb-label`}>Source</div>
+ <div className={`lb-source`}>{source}</div>
+ </div>
+ )}
+ </div>
+ <div className={`explainer`}>
+ {!loading && (
+ <div>
+ You are seeing this recommendation because this document also explores
+ <div className={`concepts-container`}>
+ {related_concepts?.map(val => {
+ return <div className={'concept'}>{val}</div>;
+ })}
+ </div>
+ </div>
+ )}
+ </div>
+ <div className={`hide-rec`}>
+ {!loading && (
+ <>
+ <div>Hide Recommendation</div>
+ <div style={{ fontSize: 15, paddingRight: 5 }}>
+ <FaEyeSlash />
+ </div>
+ </>
+ )}
+ </div>
+ </div>
+ );
+};
diff --git a/src/client/views/newlightbox/components/Recommendation/index.ts b/src/client/views/newlightbox/components/Recommendation/index.ts
new file mode 100644
index 000000000..12ebf9d6e
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/index.ts
@@ -0,0 +1,2 @@
+export * from './utils'
+export * from './Recommendation' \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Recommendation/utils.ts b/src/client/views/newlightbox/components/Recommendation/utils.ts
new file mode 100644
index 000000000..796ce0eb0
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/utils.ts
@@ -0,0 +1,23 @@
+import { DocumentType } from "../../../../documents/DocumentTypes"
+
+export interface IRecommendation {
+ loading?: boolean
+ type?: DocumentType | string,
+ data?: string,
+ title?: string,
+ text?: string,
+ source?: string,
+ previewUrl?: string,
+ transcript?: {
+ text: string,
+ start: number,
+ duration: number
+ }[],
+ embedding?: {
+ x: number,
+ y: number
+ },
+ distance?: number,
+ related_concepts?: string[],
+ docId?: string
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss
new file mode 100644
index 000000000..e541e3f3c
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss
@@ -0,0 +1,82 @@
+@import '../../NewLightboxStyles.scss';
+
+.skeletonDoc-container {
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - 40px);
+ margin: 20px;
+ gap: 20px;
+
+ .header {
+ width: calc(100% - 20px);
+ height: 80px;
+ background: $gray-l2;
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ display: grid;
+ grid-template-rows: 60% 40%;
+ padding: 10px;
+ grid-template-columns: auto auto auto auto;
+ border-radius: 20px;
+
+ .title {
+ grid-row: 1;
+ grid-column: 1 / 5;
+ display: flex;
+ width: fit-content;
+ height: 100%;
+ min-width: 500px;
+ font-size: $title-size;
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ border-radius: 20px;
+ }
+
+ .type {
+ display: flex;
+ padding: 3px 7px;
+ width: fit-content;
+ height: fit-content;
+ margin-top: 8px;
+ min-height: 15px;
+ min-width: 60px;
+ grid-row: 2;
+ grid-column: 1;
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ border-radius: 20px;
+ }
+
+ .buttons-container {
+ grid-row: 1 / 3;
+ grid-column: 5;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 10px;
+
+ .button {
+ width: 50px;
+ height: 50px;
+ border-radius: 100%;
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ }
+ }
+
+ }
+
+ .content {
+ width: 100%;
+ flex: 1;
+ -webkit-flex: 1; /* Chrome */
+ background: $gray-l2;
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ border-radius: 20px;
+ }
+
+ // &.dark {
+ // background: $black;
+ // }
+
+ // &.light,
+ // &.default {
+ // background: $white;
+ // }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx
new file mode 100644
index 000000000..50cee893f
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx
@@ -0,0 +1,22 @@
+import './SkeletonDoc.scss';
+import { ISkeletonDoc } from "./utils";
+import * as React from 'react';
+
+export const SkeletonDoc = (props: ISkeletonDoc) => {
+ const { type, data } = props
+
+ return <div className={`skeletonDoc-container`}>
+ <div className={`header`}>
+ <div className={`title`}></div>
+ <div className={`type`}></div>
+ <div className={`tags`}></div>
+ <div className={`buttons-container`}>
+ <div className={`button`}></div>
+ <div className={`button`}></div>
+ </div>
+ </div>
+ <div className={`content`}>
+ {data}
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/index.ts b/src/client/views/newlightbox/components/SkeletonDoc/index.ts
new file mode 100644
index 000000000..396b7272b
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/index.ts
@@ -0,0 +1 @@
+export * from './SkeletonDoc' \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/utils.ts b/src/client/views/newlightbox/components/SkeletonDoc/utils.ts
new file mode 100644
index 000000000..81c32c328
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/utils.ts
@@ -0,0 +1,5 @@
+import { IRecommendation } from "../Recommendation";
+
+export interface ISkeletonDoc extends IRecommendation {
+
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/Template.scss b/src/client/views/newlightbox/components/Template/Template.scss
new file mode 100644
index 000000000..5b72ddaf9
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/Template.scss
@@ -0,0 +1,15 @@
+@import '../../NewLightboxStyles.scss';
+
+.template-container {
+ width: 100vw;
+ height: 100vh;
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/Template.tsx b/src/client/views/newlightbox/components/Template/Template.tsx
new file mode 100644
index 000000000..9c6f0f59c
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/Template.tsx
@@ -0,0 +1,10 @@
+import './Template.scss';
+import * as React from 'react';
+import { ITemplate } from "./utils";
+
+export const Template = (props: ITemplate) => {
+
+ return <div className={`template-container`}>
+
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/index.ts b/src/client/views/newlightbox/components/Template/index.ts
new file mode 100644
index 000000000..36b5f3f46
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/index.ts
@@ -0,0 +1 @@
+export * from './Template' \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/utils.ts b/src/client/views/newlightbox/components/Template/utils.ts
new file mode 100644
index 000000000..965e653ec
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/utils.ts
@@ -0,0 +1,3 @@
+export interface ITemplate {
+
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/index.ts b/src/client/views/newlightbox/components/index.ts
new file mode 100644
index 000000000..3f9128690
--- /dev/null
+++ b/src/client/views/newlightbox/components/index.ts
@@ -0,0 +1,3 @@
+export * from './Template'
+export * from './Recommendation'
+export * from './SkeletonDoc' \ No newline at end of file
diff --git a/src/client/views/newlightbox/utils.ts b/src/client/views/newlightbox/utils.ts
new file mode 100644
index 000000000..6016abca4
--- /dev/null
+++ b/src/client/views/newlightbox/utils.ts
@@ -0,0 +1,121 @@
+import { DocumentType } from "../../documents/DocumentTypes";
+import { IRecommendation } from "./components";
+
+export interface IDocRequest {
+ id: string,
+ title: string,
+ text: string,
+ type: string
+}
+
+export const fetchRecommendations = async (src: string, query: string, docs?: IDocRequest[], dummy?: boolean) => {
+ console.log("[rec] making request")
+ if (dummy) {
+ return {
+ "recommendations": dummyRecs,
+ "keywords": dummyKeywords,
+ "num_recommendations": 4,
+ "max_x": 100,
+ "max_y": 100,
+ "min_x": 0,
+ "min_y": 0
+
+ };
+ }
+ const response = await fetch('http://127.0.0.1:8000/recommend', {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ "src": src,
+ "query": query,
+ "docs": docs
+ })
+ })
+ const data = await response.json();
+
+ return data;
+}
+
+export const fetchKeywords = async (text: string, n: number, dummy?: boolean) => {
+ console.log("[fetchKeywords]")
+ if (dummy) {
+ return {
+ "keywords": dummyKeywords
+ };
+ }
+ const response = await fetch('http://127.0.0.1:8000/keywords', {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ "text": text,
+ "n": n
+ })
+ })
+ const data = await response.json()
+ return data;
+}
+
+export const getType = (type: DocumentType | string) => {
+ switch(type) {
+ case DocumentType.AUDIO:
+ return "Audio"
+ case DocumentType.VID:
+ return "Video"
+ case DocumentType.PDF:
+ return "PDF"
+ case DocumentType.WEB:
+ return "Webpage"
+ case "YouTube":
+ return "Video"
+ case "HTML":
+ return "Webpage"
+ default:
+ return "Unknown: " + type
+ }
+}
+
+const dummyRecs = {
+ "a": {
+ title: 'Vannevar Bush - American Engineer',
+ previewUrl: 'https://cdn.britannica.com/98/23598-004-1E6A382E/Vannevar-Bush-Differential-Analyzer-1935.jpg',
+ type: 'web',
+ distance: 2.3,
+ source: 'www.britannica.com',
+ related_concepts: ['vannevar bush', 'knowledge'],
+ embedding: {
+ x: 0,
+ y: 0
+ }
+ },
+ "b": {
+ title: "From Memex to hypertext: Vannevar Bush and the mind's machine",
+ type: 'pdf',
+ distance: 5.4,
+ source: 'Google Scholar',
+ related_concepts: ['memex', 'vannevar bush', 'hypertext'],
+ },
+ "c": {
+ title: 'How the hyperlink changed everything | Small Thing Big Idea, a TED series',
+ previewUrl: 'https://pi.tedcdn.com/r/talkstar-photos.s3.amazonaws.com/uploads/b17d043f-2642-4117-a913-52204505513f/MargaretGouldStewart_2018V-embed.jpg?u%5Br%5D=2&u%5Bs%5D=0.5&u%5Ba%5D=0.8&u%5Bt%5D=0.03&quality=82w=640',
+ type: 'youtube',
+ distance: 5.3,
+ source: 'www.youtube.com',
+ related_concepts: ['User Control', 'Explanations']
+ },
+ "d": {
+ title: 'Recommender Systems: Behind the Scenes of Machine Learning-Based Personalization',
+ previewUrl: 'https://sloanreview.mit.edu/wp-content/uploads/2018/10/MAG-Ransbotham-Ratings-Recommendations-1200X627-1200x627.jpg',
+ type: 'pdf',
+ distance: 9.3,
+ source: 'www.altexsoft.com',
+ related_concepts: ['User Control', 'Explanations']
+ }
+}
+
+const dummyKeywords = ['user control', 'vannevar bush', 'hypermedia', 'hypertext'] \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 6558d215a..7c409c38c 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -73,7 +73,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@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 DateCast(this.dataDoc[this.fieldKey + '-recordingStart'])?.date.getTime();
+ return DateCast(this.dataDoc[this.fieldKey + '_recordingStart'])?.date.getTime();
}
@computed get rawDuration() {
return NumCast(this.dataDoc[`${this.fieldKey}_duration`]);
@@ -230,10 +230,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
recordAudioAnnotation = async () => {
this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this._recorder = new MediaRecorder(this._stream);
- this.dataDoc[this.fieldKey + '-recordingStart'] = new DateField();
+ this.dataDoc[this.fieldKey + '_recordingStart'] = new DateField();
DocUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer({file: e.data});
+ const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
if (!(result instanceof Error)) {
this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
@@ -359,9 +359,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
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');
+ const textField = Doc.LayoutFieldKey(newDoc);
+ Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc;
+ Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+ Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
if (Doc.IsInMyOverlay(this.rootDoc)) {
newDoc.overlayX = this.rootDoc.x;
newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
@@ -658,7 +659,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
CollectionFreeFormDocumentView={undefined}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
- dictationKey={this.fieldKey + '-dictation'}
+ dictationKey={this.fieldKey + '_dictation'}
mediaPath={this.path}
renderDepth={this.props.renderDepth + 1}
startTag={'_timecodeToShow' /* audioStart */}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index aae759702..1b6fe5748 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -14,6 +14,8 @@ import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth }
import './ColorBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import { RichTextMenu } from './formattedText/RichTextMenu';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { DashColor } from '../../../Utils';
@observer
export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -81,3 +83,10 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
}
+
+
+ScriptingGlobals.add(
+ function interpColors(c1:string, c2:string, weight=0.5) {
+ return DashColor(c1).mix(DashColor(c2),weight)
+ }
+) \ No newline at end of file
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index 660045a6f..a12f1c12b 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -93,4 +93,4 @@
display: flex;
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 1cc09a63c..a334e75f1 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -2,11 +2,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
-import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
@@ -32,47 +31,56 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
get clipWidthKey() {
return '_' + this.props.fieldKey + '_clipWidth';
}
-
componentDidMount() {
this.props.setContentView?.(this);
}
-
protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
this._disposers[disposerId]?.();
if (ele) {
- // create disposers identified by disposerId to remove drag & drop listeners
this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc);
}
};
@undoBatch
- private internalDrop = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
+ private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
if (dropEvent.complete.docDragData) {
- event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
- droppedDocs.lastElement().embedContainer = this.dataDoc;
- this.dataDoc[fieldKey] = droppedDocs.lastElement();
+ const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
+ Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc);
+ !added && e.preventDefault();
+ e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
+ return added;
}
};
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
- e.button !== 2 &&
+ if (e.button !== 2) {
setupMoveUpEvents(
this,
e,
this.onPointerMove,
emptyFunction,
+ action((e, doubleTap) => {
+ if (doubleTap) {
+ this._isAnyChildContentActive = true;
+ if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ }
+ }),
+ false,
+ undefined,
action(() => {
- // on click, animate slider movement to the targetWidth
+ if (this._isAnyChildContentActive) return;
this._animating = 'all 200ms';
+ // on click, animate slider movement to the targetWidth
this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this.props.PanelWidth();
setTimeout(
action(() => (this._animating = '')),
200
);
- }),
- false
+ })
);
+ }
};
@action
@@ -85,10 +93,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const anchor = Docs.Create.ComparisonConfigDocument({
- title: 'ImgAnchor:' + this.rootDoc.title,
+ const anchor = Docs.Create.ConfigDocument({
+ title: 'CompareAnchor:' + this.rootDoc.title,
// set presentation timing properties for restoring view
- presTransition: 1000,
+ presentation_transition: 1000,
annotationOn: this.rootDoc,
});
if (anchor) {
@@ -105,12 +113,29 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
e.stopPropagation; // prevent click event action (slider movement) in registerSliding
delete this.dataDoc[fieldKey];
};
+ moveDoc = (doc: Doc, addDocument: (document: Doc | Doc[]) => boolean, which: string) => this.remDoc(doc, which) && addDocument(doc);
+ addDoc = (doc: Doc, which: string) => {
+ this.dataDoc[which] = doc;
+ return true;
+ };
+ remDoc = (doc: Doc, which: string) => {
+ if (this.dataDoc[which] === doc) {
+ this.dataDoc[which] = undefined;
+ return true;
+ }
+ return false;
+ };
+
+ whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));
docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
if (property === StyleProp.PointerEvents) return 'none';
return this.props.styleProvider?.(doc, props, property);
};
-
+ moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
+ moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
+ remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
+ remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
render() {
const clearButton = (which: string) => {
return (
@@ -118,33 +143,35 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
className={`clear-button ${which}`}
onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
onClick={e => this.clearDoc(e, which)}>
- <FontAwesomeIcon className={`clear-button ${which}`} icon={'times'} size="sm" />
+ <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) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
- const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc;
- return whichDoc ? (
+ const whichDoc = DocCast(this.dataDoc[which]);
+ const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
+ return targetDoc ? (
<>
<DocumentView
{...this.props}
+ Document={targetDoc}
+ DataDoc={undefined}
+ moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
+ removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
NativeWidth={returnZero}
NativeHeight={returnZero}
- isContentActive={returnFalse}
+ isContentActive={emptyFunction}
isDocumentActive={returnFalse}
- styleProvider={this.docStyleProvider}
- Document={targetDoc}
- DataDoc={undefined}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ styleProvider={this._isAnyChildContentActive ? this.props.styleProvider : this.docStyleProvider}
hideLinkButton={true}
- pointerEvents={returnNone}
+ pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
/>
{clearButton(which)}
</> // placeholder image if doc is missing
) : (
<div className="placeholder">
- <FontAwesomeIcon className="upload-icon" icon={'cloud-upload-alt'} size="lg" />
+ <FontAwesomeIcon className="upload-icon" icon="cloud-upload-alt" size="lg" />
</div>
);
};
@@ -157,7 +184,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
return (
- <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+ <div className={`comparisonBox${this.props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
{displayBox(`${this.fieldKey}_2`, 1, this.props.PanelWidth() - 3)}
<div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
{displayBox(`${this.fieldKey}_1`, 0, 0)}
@@ -169,7 +196,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
left: `calc(${this.clipWidth + '%'} - 0.5px)`,
cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? 'w-resize' : undefined,
}}
- onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+ onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
>
<div className="slide-handle" />
</div>
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index cd500e9ae..a69881b7c 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -1,4 +1,14 @@
.dataviz {
- overflow: auto;
+ overflow: scroll;
height: 100%;
+ width: 100%;
+
+ .datatype-button{
+ display: flex;
+ flex-direction: row;
+ }
+}
+.start-message {
+ margin: 10px;
+ align-self: baseline;
}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index baa45e278..c07ab5ba1 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,9 +1,9 @@
-import { action, computed, observable, ObservableMap, ObservableSet } from 'mobx';
+import { action, computed, ObservableMap } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, StrListCast } from '../../../../fields/Doc';
+import { Doc, Field, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
-import { Cast, CsvCast, NumCast, StrCast } from '../../../../fields/Types';
+import { Cast, CsvCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { Docs } from '../../../documents/Documents';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -12,56 +12,49 @@ import { PinProps } from '../trails';
import { LineChart } from './components/LineChart';
import { TableBox } from './components/TableBox';
import './DataVizBox.scss';
+import { Histogram } from './components/Histogram';
+import { PieChart } from './components/PieChart';
+import { Toggle, ToggleType, Type } from 'browndash-components';
export enum DataVizView {
TABLE = 'table',
LINECHART = 'lineChart',
+ HISTOGRAM = 'histogram',
+ PIECHART = 'pieChart',
}
@observer
export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(DataVizBox, fieldKey);
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(DataVizBox, fieldStr);
}
- // says we have an object and any string
- // 2 ways of doing it
- // @observable private pairs: { [key: string]: number | string | undefined }[] = [];
- // @observable private pairs: { [key: string]: FieldResult }[] = [];
+
+ // all data
static pairSet = new ObservableMap<string, { [key: string]: string }[]>();
@computed.struct get pairs() {
return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
}
- private _chartRenderer: LineChart | undefined;
- // // another way would be store a schema that defines the type of data we are expecting from an imported doc
-
- // method1() {
- // this.pairs[0].x = 3;
- // }
-
- // method() {
- // // this.pairs[0].x = 3;
- // // go through the pairs
- // const x = this.pairs[0].x;
- // if (typeof x == 'number') {
- // let x1 = Number(x);
- // // let x1 = NumCast(x);
- // }
- // }
- // could use field result
- // [key: string]: FieldResult;
- // instead of numeric x,y in there,
-
- // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops
+ private _chartRenderer: LineChart | Histogram | PieChart | undefined;
+ // current displayed chart type
@computed get dataVizView(): DataVizView {
- return StrCast(this.layoutDoc._dataVizView, 'table') as DataVizView;
+ return StrCast(this.layoutDoc._dataViz, 'table') as DataVizView;
}
- @action
+ @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
- const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView);
- const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._data_vizAxes = new List<string>(StrListCast(data.presDataVizAxes)));
+ const changedView = this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
+ const changedAxes = this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List<string>(StrListCast(data.config_dataVizAxes)));
+ this.layoutDoc.dataViz_selectedRows = Field.Copy(data.dataViz_selectedRows);
+ this.layoutDoc.histogramBarColors = Field.Copy(data.histogramBarColors);
+ this.layoutDoc.defaultHistogramColor = data.defaultHistogramColor;
+ this.layoutDoc.pieSliceColors = Field.Copy(data.pieSliceColors);
+ Object.keys(this.layoutDoc).map(key => {
+ if (key.startsWith('histogram_title') || key.startsWith('lineChart_title') || key.startsWith('pieChart_title')) {
+ this.layoutDoc['_' + key] = data[key];
+ }
+ });
const func = () => this._chartRenderer?.restoreView(data);
if (changedView || changedAxes) {
setTimeout(func, 100);
@@ -69,39 +62,92 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
return func() ?? false;
};
-
getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
- const anchor =
- this._chartRenderer?.getAnchor(pinProps) ??
- Docs.Create.DataVizConfigDocument({
- // when we clear selection -> we should have it so chartBox getAnchor returns undefined
- // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
- /*put in some options*/
- });
-
- anchor.presDataVizView = this.dataVizView;
- anchor.presDataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined;
-
+ const anchor = !pinProps
+ ? this.rootDoc
+ : this._chartRenderer?.getAnchor(pinProps) ??
+ Docs.Create.ConfigDocument({
+ // when we clear selection -> we should have it so chartBox getAnchor returns undefined
+ // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
+ /*put in some options*/
+ });
+ anchor.config_dataViz = this.dataVizView;
+ anchor.config_dataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined;
+ anchor.dataViz_selectedRows = Field.Copy(this.layoutDoc.dataViz_selectedRows);
+ anchor.histogramBarColors = Field.Copy(this.layoutDoc.histogramBarColors);
+ anchor.defaultHistogramColor = this.layoutDoc.defaultHistogramColor;
+ anchor.pieSliceColors = Field.Copy(this.layoutDoc.pieSliceColors);
+ Object.keys(this.layoutDoc).map(key => {
+ if (key.startsWith('histogram_title') || key.startsWith('lineChart_title') || key.startsWith('pieChart_title')) {
+ anchor[key] = this.layoutDoc[key];
+ }
+ });
this.addDocument(anchor);
return anchor;
};
@computed.struct get axes() {
- return StrListCast(this.layoutDoc.data_vizAxes);
+ return StrListCast(this.layoutDoc.dataViz_axes);
}
- selectAxes = (axes: string[]) => (this.layoutDoc.data_vizAxes = new List<string>(axes));
+ selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));
+ // toggles for user to decide which chart type to view the data in
@computed get selectView() {
const width = this.props.PanelWidth() * 0.9;
const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9;
- const margin = { top: 10, right: 25, bottom: 50, left: 25 };
+ const margin = { top: 10, right: 25, bottom: 75, left: 45 };
if (!this.pairs) return 'no data';
- // prettier-ignore
switch (this.dataVizView) {
- case DataVizView.TABLE: return <TableBox pairs={this.pairs} axes={this.axes} docView={this.props.DocumentView} selectAxes={this.selectAxes}/>;
- case DataVizView.LINECHART: return <LineChart ref={r => (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />;
+ case DataVizView.TABLE:
+ return <TableBox layoutDoc={this.layoutDoc} pairs={this.pairs} axes={this.axes} height={height} width={width} margin={margin} rootDoc={this.rootDoc} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.LINECHART:
+ return (
+ <LineChart
+ layoutDoc={this.layoutDoc}
+ ref={r => (this._chartRenderer = r ?? undefined)}
+ height={height}
+ width={width}
+ fieldKey={this.fieldKey}
+ margin={margin}
+ rootDoc={this.rootDoc}
+ axes={this.axes}
+ pairs={this.pairs}
+ dataDoc={this.dataDoc}
+ />
+ );
+ case DataVizView.HISTOGRAM:
+ return (
+ <Histogram
+ layoutDoc={this.layoutDoc}
+ ref={r => (this._chartRenderer = r ?? undefined)}
+ height={height}
+ width={width}
+ fieldKey={this.fieldKey}
+ margin={margin}
+ rootDoc={this.rootDoc}
+ axes={this.axes}
+ pairs={this.pairs}
+ dataDoc={this.dataDoc}
+ />
+ );
+ case DataVizView.PIECHART:
+ return (
+ <PieChart
+ layoutDoc={this.layoutDoc}
+ ref={r => (this._chartRenderer = r ?? undefined)}
+ height={height}
+ width={width}
+ fieldKey={this.fieldKey}
+ margin={margin}
+ rootDoc={this.rootDoc}
+ axes={this.axes}
+ pairs={this.pairs}
+ dataDoc={this.dataDoc}
+ />
+ );
}
}
+
@computed get dataUrl() {
return Cast(this.dataDoc[this.fieldKey], CsvField);
}
@@ -109,6 +155,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
componentDidMount() {
this.props.setContentView?.(this);
this.fetchData();
+ if (!this.layoutDoc._dataViz) this.layoutDoc._dataViz = this.dataVizView;
}
fetchData() {
@@ -118,16 +165,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
.then(res => res.json().then(action(res => !res.errno && DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res))));
}
- // handle changing the view using a button
- @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) {
- e.preventDefault();
- e.stopPropagation();
- this.layoutDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE;
- }
-
render() {
return !this.pairs?.length ? (
- <div>Loading...</div>
+ // displays how to get data into the DataVizBox if its empty
+ <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
) : (
<div
className="dataViz"
@@ -142,7 +183,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
{ passive: false }
)
}>
- <button onClick={e => this.changeViewHandler(e)}>{this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE}</button>
+ <div className={'datatype-button'}>
+ <Toggle text={'TABLE'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz == DataVizView.TABLE} />
+ <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.LINECHART} />
+ <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz == DataVizView.HISTOGRAM} />
+ <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.PIECHART} />
+ </div>
{this.selectView}
</div>
);
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index d4f7bfb32..35e5187b2 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -3,6 +3,43 @@
flex-direction: column;
align-items: center;
cursor: default;
+ margin-top: 10px;
+ overflow-y: visible;
+
+ .graph{
+ overflow: visible;
+ }
+ .graph-title{
+ align-items: center;
+ font-size: larger;
+ display: flex;
+ flex-direction: row;
+ margin-top: -10px;
+ margin-bottom: -10px;
+ }
+ .selected-data{
+ align-items: center;
+ text-align: center;
+ display: flex;
+ flex-direction: row;
+ margin: 10px;
+ margin-top: -25px;
+ margin-bottom: 5px;
+ }
+ .slice {
+ &.hover {
+ stroke: black;
+ stroke-width: 2px;
+ }
+ }
+
+ .histogram-bar{
+ outline: thin solid black;
+ &.hover{
+ outline: 3px solid black;
+ outline-offset: -3px;
+ }
+ }
.tooltip {
// make the height width bigger
@@ -39,3 +76,9 @@
fill: red;
}
}
+.table-container{
+ overflow: scroll;
+ margin: 10px;
+ margin-left: 25px;
+ margin-top: 25px;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
new file mode 100644
index 000000000..b3bdccbbb
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -0,0 +1,523 @@
+import { observer } from 'mobx-react';
+import { Doc, StrListCast } from '../../../../../fields/Doc';
+import * as React from 'react';
+import * as d3 from 'd3';
+import { IReactionDisposer, action, computed, observable, reaction } from 'mobx';
+import { LinkManager } from '../../../../util/LinkManager';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { PinProps, PresBox } from '../../trails';
+import { Docs } from '../../../../documents/Documents';
+import { List } from '../../../../../fields/List';
+import './Chart.scss';
+import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
+import { FaFillDrip } from 'react-icons/fa';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { listSpec } from '../../../../../fields/Schema';
+import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
+import { undoBatch, undoable } from '../../../../util/UndoManager';
+
+export interface HistogramProps {
+ rootDoc: Doc;
+ layoutDoc: Doc;
+ axes: string[];
+ pairs: { [key: string]: any }[];
+ width: number;
+ height: number;
+ dataDoc: Doc;
+ fieldKey: string;
+ margin: {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+ };
+}
+
+@observer
+export class Histogram extends React.Component<HistogramProps> {
+ private _disposers: { [key: string]: IReactionDisposer } = {};
+ private _histogramRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _histogramSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
+ private numericalXData: boolean = false; // whether the data is organized by numbers rather than categoreis
+ private numericalYData: boolean = false; // whether the y axis is controlled by provided data rather than frequency
+ private maxBins = 15; // maximum number of bins that is readable on a normal sized doc
+ @observable _currSelected: any | undefined = undefined; // Object of selected bar
+ private curBarSelected: any = undefined; // histogram bin of selected bar
+ private selectedData: any = undefined; // Selection of selected bar
+ private hoverOverData: any = undefined; // Selection of bar being hovered over
+
+ // filters all data to just display selected data if brushed (created from an incoming link)
+ @computed get _histogramData() {
+ var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids);
+ if (this.props.axes.length < 1) return [];
+ if (this.props.axes.length < 2) {
+ var ax0 = this.props.axes[0];
+ if (/\d/.test(this.props.pairs[0][ax0])) {
+ this.numericalXData = true;
+ }
+ return this.props.pairs
+ ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
+ .map(pair => ({ [ax0]: pair[this.props.axes[0]] }));
+ }
+ var ax0 = this.props.axes[0];
+ var ax1 = this.props.axes[1];
+ if (/\d/.test(this.props.pairs[0][ax0])) {
+ this.numericalXData = true;
+ }
+ if (/\d/.test(this.props.pairs[0][ax1])) {
+ this.numericalYData = true;
+ }
+ return this.props.pairs
+ ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
+ .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] }));
+ }
+
+ @computed get defaultGraphTitle() {
+ var ax0 = this.props.axes[0];
+ var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
+ if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.pairs[0][ax1]) || !this.numericalYData) {
+ return ax0 + ' Histogram';
+ } else return ax0 + ' by ' + ax1 + ' Histogram';
+ }
+
+ @computed get incomingLinks() {
+ return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+ .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link
+ .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
+ }
+
+ @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
+ if (this.numericalXData) {
+ const data = this.data(this._histogramData);
+ return { xMin: Math.min.apply(null, data), xMax: Math.max.apply(null, data), yMin: 0, yMax: 0 };
+ }
+ return { xMin: 0, xMax: 0, yMin: 0, yMax: 0 };
+ }
+
+ componentWillUnmount() {
+ Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
+ }
+ componentDidMount = () => {
+ this._disposers.chartData = reaction(
+ () => ({ dataSet: this._histogramData, w: this.width, h: this.height }),
+ ({ dataSet, w, h }) => {
+ if (dataSet!.length > 0) {
+ this.drawChart(dataSet, w, h);
+ }
+ },
+ { fireImmediately: true }
+ );
+ };
+
+ @action
+ restoreView = (data: Doc) => {};
+ // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
+ getAnchor = (pinProps?: PinProps) => {
+ const anchor = Docs.Create.ConfigDocument({
+ //
+ title: 'histogram doc selection' + this._currSelected,
+ });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+ return anchor;
+ };
+
+ @computed get height() {
+ return this.props.height - this.props.margin.top - this.props.margin.bottom;
+ }
+
+ @computed get width() {
+ return this.props.width - this.props.margin.left - this.props.margin.right;
+ }
+
+ // cleans data by converting numerical data to numbers and taking out empty cells
+ data = (dataSet: any) => {
+ var validData = dataSet.filter((d: { [x: string]: unknown }) => {
+ var valid = true;
+ Object.keys(dataSet[0]).map(key => {
+ if (!d[key] || Number.isNaN(d[key])) valid = false;
+ });
+ return valid;
+ });
+ var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
+ const data = validData.map((d: { [x: string]: any }) => {
+ if (this.numericalXData) {
+ return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
+ }
+ return d[field!];
+ });
+ return data;
+ };
+
+ // outlines the bar selected / hovered over
+ highlightSelectedBar = (changeSelectedVariables: boolean, svg: any, eachRectWidth: any, pointerX: any, xAxisTitle: any, yAxisTitle: any, histDataSet: any) => {
+ var sameAsCurrent: boolean;
+ var barCounter = -1;
+ const selected = svg.selectAll('.histogram-bar').filter((d: any) => {
+ barCounter++; // uses the order of bars and width of each bar to find which one the pointer is over
+ if (barCounter * eachRectWidth <= pointerX && pointerX <= (barCounter + 1) * eachRectWidth) {
+ var showSelected = this.numericalYData
+ ? this._histogramData.filter((data: { [x: string]: any }) => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0]
+ : histDataSet.filter((data: { [x: string]: any }) => data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0];
+ if (this.numericalXData) {
+ // calculating frequency
+ if (d[0] && d[1] && d[0] != d[1]) {
+ showSelected = { [xAxisTitle]: d3.min(d) + ' to ' + d3.max(d), frequency: d.length };
+ } else if (!this.numericalYData) showSelected = { [xAxisTitle]: showSelected[xAxisTitle], frequency: d.length };
+ }
+ if (changeSelectedVariables) {
+ // for when a bar is selected - not just hovered over
+ sameAsCurrent = this._currSelected ? showSelected[xAxisTitle] == this._currSelected![xAxisTitle] && showSelected[yAxisTitle] == this._currSelected![yAxisTitle] : false;
+ this._currSelected = sameAsCurrent ? undefined : showSelected;
+ this.selectedData = sameAsCurrent ? undefined : d;
+ } else this.hoverOverData = d;
+ return true;
+ }
+ return false;
+ });
+ if (changeSelectedVariables) {
+ if (sameAsCurrent!) this.curBarSelected = undefined;
+ else this.curBarSelected = selected;
+ }
+ };
+
+ // draws the histogram
+ drawChart = (dataSet: any, width: number, height: number) => {
+ d3.select(this._histogramRef.current).select('svg').remove();
+ d3.select(this._histogramRef.current).select('.tooltip').remove();
+
+ var data = this.data(dataSet);
+ var xAxisTitle = Object.keys(dataSet[0])[0];
+ var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
+ let uniqueArr: unknown[] = [...new Set(data)];
+ var numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
+ var translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
+ if (numBins > this.maxBins) numBins = this.maxBins;
+ var startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
+ var endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
+
+ // converts data into Objects
+ var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => {
+ var valid = true;
+ Object.keys(dataSet[0]).map(key => {
+ if (!d[key] || Number.isNaN(d[key])) valid = false;
+ });
+ return valid;
+ });
+ if (!this.numericalXData) {
+ var histStringDataSet: { [x: string]: unknown }[] = [];
+ if (this.numericalYData) {
+ for (let i = 0; i < dataSet.length; i++) {
+ histStringDataSet.push({ [yAxisTitle]: dataSet[i][yAxisTitle], [xAxisTitle]: dataSet[i][xAxisTitle] });
+ }
+ } else {
+ for (let i = 0; i < uniqueArr.length; i++) {
+ histStringDataSet.push({ [yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i] });
+ }
+ for (let i = 0; i < data.length; i++) {
+ let barData = histStringDataSet.filter(each => each[xAxisTitle] == data[i]);
+ histStringDataSet.filter(each => each[xAxisTitle] == data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1;
+ }
+ }
+ histDataSet = histStringDataSet;
+ }
+
+ // initial graph and binning data for histogram
+ var svg = (this._histogramSvg = d3
+ .select(this._histogramRef.current)
+ .append('svg')
+ .attr('class', 'graph')
+ .attr('width', width + this.props.margin.right + this.props.margin.left)
+ .attr('height', height + this.props.margin.top + this.props.margin.bottom)
+ .append('g')
+ .attr('transform', 'translate(' + this.props.margin.left + ',' + this.props.margin.top + ')'));
+ var x = d3
+ .scaleLinear()
+ .domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
+ .range([0, width]);
+ var histogram = d3
+ .histogram()
+ .value(function (d) {
+ return d;
+ })
+ .domain([startingPoint!, endingPoint!])
+ .thresholds(x.ticks(numBins));
+ var bins = histogram(data);
+ var eachRectWidth = width / bins.length;
+ var graphStartingPoint = bins[0].x1 && bins[1] ? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0;
+ bins[0].x0 = graphStartingPoint;
+ x = x.domain([graphStartingPoint, endingPoint]).range([0, Number.isInteger(this.rangeVals.xMin!) ? width - eachRectWidth : width]);
+ var xAxis;
+
+ // more calculations based on bins
+ // x-axis
+ if (!this.numericalXData) {
+ // reorganize to match data if the data is strings rather than numbers
+ // uniqueArr.sort()
+ histDataSet.sort();
+ for (let i = 0; i < data.length; i++) {
+ var index = 0;
+ for (let j = 0; j < uniqueArr.length; j++) {
+ if (uniqueArr[j] == data[i]) {
+ index = j;
+ }
+ }
+ if (bins[index]) bins[index].push(data[i]);
+ }
+ bins.pop();
+ eachRectWidth = width / bins.length;
+ bins.forEach(d => (d.x0 = d.x0!));
+ xAxis = d3
+ .axisBottom(x)
+ .ticks(bins.length > 1 ? bins.length - 1 : 1)
+ .tickFormat(i => uniqueArr[i.valueOf()] as string)
+ .tickPadding(10);
+ x.range([0, width - eachRectWidth]);
+ x.domain([0, bins.length - 1]);
+ translateXAxis = eachRectWidth / 2;
+ } else {
+ var allSame = true;
+ for (var i = 0; i < bins.length; i++) {
+ if (bins[i] && bins[i][0]) {
+ var compare = bins[i][0];
+ for (let j = 1; j < bins[i].length; j++) {
+ if (bins[i][j] != compare) allSame = false;
+ }
+ }
+ }
+ if (allSame) {
+ translateXAxis = eachRectWidth / 2;
+ eachRectWidth = width / bins.length;
+ } else {
+ eachRectWidth = width / (bins.length + 1);
+ var tickDiff = bins.length >= 2 ? bins[bins.length - 2].x1! - bins[bins.length - 2].x0! : 0;
+ var curDomain = x.domain();
+ x.domain([curDomain[0], curDomain[0] + tickDiff * bins.length]);
+ }
+
+ xAxis = d3.axisBottom(x).ticks(bins.length - 1);
+ x.range([0, width - eachRectWidth]);
+ }
+ // y-axis
+ const maxFrequency = this.numericalYData
+ ? d3.max(histDataSet, function (d: any) {
+ return d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) : 0;
+ })
+ : d3.max(bins, function (d) {
+ return d.length;
+ });
+ var y = d3.scaleLinear().range([height, 0]);
+ y.domain([0, +maxFrequency!]);
+ var yAxis = d3.axisLeft(y).ticks(maxFrequency!);
+ if (this.numericalYData) {
+ const yScale = scaleCreatorNumerical(0, Number(maxFrequency), height, 0);
+ yAxisCreator(svg.append('g'), width, yScale);
+ } else {
+ svg.append('g').call(yAxis);
+ }
+ svg.append('g')
+ .attr('transform', 'translate(' + translateXAxis + ', ' + height + ')')
+ .call(xAxis);
+
+ // click/hover
+ const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet));
+ const onHover = action((e: any) => {
+ const selected = this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
+ updateHighlights();
+ });
+ const mouseOut = action((e: any) => {
+ this.hoverOverData = undefined;
+ updateHighlights();
+ });
+ const updateHighlights = () => {
+ const hoverOverBar = this.hoverOverData;
+ const selectedData = this.selectedData;
+ svg.selectAll('rect').attr('class', function (d: any) {
+ return (hoverOverBar && hoverOverBar[0] == d[0]) || (selectedData && selectedData[0] == d[0]) ? 'histogram-bar hover' : 'histogram-bar';
+ });
+ };
+ svg.on('click', onPointClick).on('mouseover', onHover).on('mouseout', mouseOut);
+
+ // axis titles
+ svg.append('text')
+ .attr('transform', 'translate(' + width / 2 + ' ,' + (height + 40) + ')')
+ .style('text-anchor', 'middle')
+ .text(xAxisTitle);
+ svg.append('text')
+ .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+ .attr('x', -(height / 2))
+ .attr('y', -20)
+ .style('text-anchor', 'middle')
+ .text(yAxisTitle);
+ d3.format('.0f');
+
+ // draw bars
+ var selected = this.selectedData;
+ svg.selectAll('rect')
+ .data(bins)
+ .enter()
+ .append('rect')
+ .attr(
+ 'transform',
+ this.numericalYData
+ ? function (d) {
+ var eachData = histDataSet.filter((data: { [x: string]: number }) => {
+ return data[xAxisTitle] == d[0];
+ });
+ var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ return 'translate(' + x(d.x0!) + ',' + y(length) + ')';
+ }
+ : function (d) {
+ return 'translate(' + x(d.x0!) + ',' + y(d.length) + ')';
+ }
+ )
+ .attr(
+ 'height',
+ this.numericalYData
+ ? function (d) {
+ var eachData = histDataSet.filter((data: { [x: string]: number }) => {
+ return data[xAxisTitle] == d[0];
+ });
+ var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+ return height - y(length);
+ }
+ : function (d) {
+ return height - y(d.length);
+ }
+ )
+ .attr('width', eachRectWidth)
+ .attr(
+ 'class',
+ selected
+ ? function (d) {
+ return selected && selected[0] == d[0] ? 'histogram-bar hover' : 'histogram-bar';
+ }
+ : function (d) {
+ return 'histogram-bar';
+ }
+ )
+ .attr('fill', d => {
+ var barColor;
+ var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+ barColors.map(each => {
+ if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
+ else {
+ var range = StrCast(each[0]).split(' to ');
+ if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
+ }
+ });
+ return barColor ? StrCast(barColor) : StrCast(this.props.layoutDoc.defaultHistogramColor);
+ });
+ };
+
+ @action changeSelectedColor = (color: string) => {
+ this.curBarSelected.attr('fill', color);
+ var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+
+ const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec('string'), null);
+ barColors.map(each => {
+ if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1);
+ });
+ barColors.push(StrCast(barName + '::' + color));
+ };
+
+ @action eraseSelectedColor = () => {
+ this.curBarSelected.attr('fill', this.props.layoutDoc.defaultHistogramColor);
+ var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+
+ const barColors = Cast(this.props.layoutDoc.histogramBarColors, listSpec('string'), null);
+ barColors.map(each => {
+ if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1);
+ });
+ };
+
+ render() {
+ this._histogramData;
+ var curSelectedBarName = '';
+ var titleAccessor: any = '';
+ if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_histogram_' + this.props.axes[0] + '-' + this.props.axes[1];
+ else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_histogram_' + this.props.axes[0];
+ if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+ if (!this.props.layoutDoc.defaultHistogramColor) this.props.layoutDoc.defaultHistogramColor = '#69b3a2';
+ if (!this.props.layoutDoc.histogramBarColors) this.props.layoutDoc.histogramBarColors = new List<string>();
+ var selected: string;
+ if (this._currSelected) {
+ curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ selected = '{ ';
+ Object.keys(this._currSelected).map(key => {
+ key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
+ });
+ selected = selected.substring(0, selected.length - 2);
+ selected += ' }';
+ } else selected = 'none';
+ var selectedBarColor;
+ var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+ barColors.map(each => {
+ if (each[0] == curSelectedBarName!) selectedBarColor = each[1];
+ });
+
+ this.componentDidMount();
+
+ if (this._histogramData.length > 0 || (!this.incomingLinks || this.incomingLinks.length==0)) {
+ return this.props.axes.length >= 1 ? (
+ <div className="chart-container">
+ <div className="graph-title">
+ <EditableText
+ val={StrCast(this.props.layoutDoc[titleAccessor])}
+ setVal={undoable(
+ action(val => (this.props.layoutDoc[titleAccessor] = val as string)),
+ 'Change Graph Title'
+ )}
+ color={'black'}
+ size={Size.LARGE}
+ fillWidth
+ />
+ &nbsp; &nbsp;
+ <ColorPicker
+ tooltip={'Change Default Bar Color'}
+ type={Type.SEC}
+ icon={<FaFillDrip />}
+ selectedColor={StrCast(this.props.layoutDoc.defaultHistogramColor)}
+ setFinalColor={undoable(color => (this.props.layoutDoc.defaultHistogramColor = color), 'Change Default Bar Color')}
+ setSelectedColor={undoable(color => (this.props.layoutDoc.defaultHistogramColor = color), 'Change Default Bar Color')}
+ size={Size.XSMALL}
+ />
+ </div>
+ <div ref={this._histogramRef} />
+ {selected != 'none' ? (
+ <div className={'selected-data'}>
+ Selected: {selected}
+ &nbsp; &nbsp;
+ <ColorPicker
+ tooltip={'Change Bar Color'}
+ type={Type.SEC}
+ icon={<FaFillDrip />}
+ selectedColor={selectedBarColor ? selectedBarColor : this.curBarSelected.attr('fill')}
+ setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
+ setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
+ size={Size.XSMALL}
+ />
+ &nbsp;
+ <IconButton
+ icon={<FontAwesomeIcon icon={'eraser'} />}
+ size={Size.XSMALL}
+ color={'black'}
+ type={Type.SEC}
+ tooltip={'Revert to the default bar color'}
+ onClick={undoable(
+ action(() => this.eraseSelectedColor()),
+ 'Change Selected Bar Color'
+ )}
+ />
+ </div>
+ ) : null}
+ </div>
+ ) : (
+ <span className="chart-container"> {'first use table view to select a column to graph'}</span>
+ );
+ } else
+ return (
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 661061d51..46cf27705 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,13 +1,12 @@
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-// import d3
import * as d3 from 'd3';
-import { Doc, DocListCast } from '../../../../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../../../../fields/Doc';
import { Id } from '../../../../../fields/FieldSymbols';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
-import { Cast, DocCast } from '../../../../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
import { Docs } from '../../../../documents/Documents';
import { DocumentManager } from '../../../../util/DocumentManager';
import { LinkManager } from '../../../../util/LinkManager';
@@ -15,16 +14,19 @@ import { PinProps, PresBox } from '../../trails';
import { DataVizBox } from '../DataVizBox';
import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
import './Chart.scss';
+import { EditableText, Size } from 'browndash-components';
+import { undoable } from '../../../../util/UndoManager';
export interface DataPoint {
x: number;
y: number;
}
-interface SelectedDataPoint extends DataPoint {
+export interface SelectedDataPoint extends DataPoint {
elem?: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
}
export interface LineChartProps {
rootDoc: Doc;
+ layoutDoc: Doc;
axes: string[];
pairs: { [key: string]: any }[];
width: number;
@@ -48,18 +50,26 @@ export class LineChart extends React.Component<LineChartProps> {
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
@computed get _lineChartData() {
+ var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids);
if (this.props.axes.length <= 1) return [];
return this.props.pairs
- ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select'))))
+ ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) }))
.sort((a, b) => (a.x < b.x ? -1 : 1));
}
+ @computed get graphTitle() {
+ return this.props.axes[1] + ' vs. ' + this.props.axes[0] + ' Line Chart';
+ }
@computed get incomingLinks() {
return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
- .filter(link => link.link_anchor_1 !== this.props.rootDoc) // get links where this chart doc is the target of the link
+ .filter(link => {
+ return link.link_anchor_1 == this.props.rootDoc.draggedFrom;
+ }) // get links where this chart doc is the target of the link
.map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@computed get incomingSelected() {
+ // return selected x and y axes
+ // otherwise, use the selection of whatever is linked to us
return this.incomingLinks // all links that are pointing to this node
.map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes
.filter(dvb => dvb)
@@ -149,7 +159,7 @@ export class LineChart extends React.Component<LineChartProps> {
@action
restoreView = (data: Doc) => {
- const coords = Cast(data.presDataVizSelection, listSpec('number'), null);
+ const coords = Cast(data.config_dataVizSelection, listSpec('number'), null);
if (coords?.length > 1 && (this._currSelected?.x !== coords[0] || this._currSelected?.y !== coords[1])) {
this.setCurrSelected(coords[0], coords[1]);
return true;
@@ -163,12 +173,12 @@ export class LineChart extends React.Component<LineChartProps> {
// create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
getAnchor = (pinProps?: PinProps) => {
- const anchor = Docs.Create.LineChartConfigDocument({
+ const anchor = Docs.Create.ConfigDocument({
//
title: 'line doc selection' + this._currSelected?.x,
});
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc);
- anchor.presDataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+ anchor.config_dataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
return anchor;
};
@@ -180,6 +190,14 @@ export class LineChart extends React.Component<LineChartProps> {
return this.props.width - this.props.margin.left - this.props.margin.right;
}
+ @computed get defaultGraphTitle() {
+ var ax0 = this.props.axes[0];
+ var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
+ if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) {
+ return ax0 + ' Line Chart';
+ } else return ax1 + ' by ' + ax0 + ' Line Chart';
+ }
+
setupTooltip() {
return d3
.select(this._lineChartRef.current)
@@ -197,9 +215,9 @@ export class LineChart extends React.Component<LineChartProps> {
@action
setCurrSelected(x?: number, y?: number) {
// TODO: nda - get rid of svg element in the list?
- this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
+ if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = undefined;
+ else this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true));
- this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined));
}
drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -219,7 +237,7 @@ export class LineChart extends React.Component<LineChartProps> {
}
// TODO: nda - can use d3.create() to create html element instead of appending
- drawChart = (dataSet: DataPoint[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
+ drawChart = (dataSet: any[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
// clearing tooltip and the current chart
d3.select(this._lineChartRef.current).select('svg').remove();
d3.select(this._lineChartRef.current).select('.tooltip').remove();
@@ -238,6 +256,7 @@ export class LineChart extends React.Component<LineChartProps> {
const svg = (this._lineChartSvg = d3
.select(this._lineChartRef.current)
.append('svg')
+ .attr('class', 'graph')
.attr('width', `${width + margin.left + margin.right}`)
.attr('height', `${height + margin.top + margin.bottom}`)
.append('g')
@@ -249,13 +268,20 @@ export class LineChart extends React.Component<LineChartProps> {
xAxisCreator(svg.append('g'), height, xScale);
yAxisCreator(svg.append('g'), width, yScale);
- // draw the plot line
+ // get valid data points
const data = dataSet[0];
const lineGen = createLineGenerator(xScale, yScale);
- drawLine(svg.append('path'), data, lineGen);
-
+ var validData = data.filter(d => {
+ var valid = true;
+ Object.keys(data[0]).map(key => {
+ if (!d[key] || Number.isNaN(d[key])) valid = false;
+ });
+ return valid;
+ });
+ // draw the plot line
+ drawLine(svg.append('path'), validData, lineGen);
// draw the datapoint circle
- this.drawDataPoints(data, 0, xScale, yScale);
+ this.drawDataPoints(validData, 0, xScale, yScale);
const higlightFocusPt = svg.append('g').style('display', 'none');
higlightFocusPt.append('circle').attr('r', 5).attr('class', 'circle');
@@ -293,6 +319,20 @@ export class LineChart extends React.Component<LineChartProps> {
.on('mouseout', () => tooltip.transition().duration(300).style('opacity', 0))
.on('mousemove', mousemove)
.on('click', onPointClick);
+
+ // axis titles
+ svg.append('text')
+ .attr('transform', 'translate(' + width / 2 + ' ,' + (height + 40) + ')')
+ .style('text-anchor', 'middle')
+ .text(this.props.axes[0]);
+ svg.append('text')
+ .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+ .attr('x', -(height / 2))
+ .attr('y', -20)
+ .attr('height', 20)
+ .attr('width', 20)
+ .style('text-anchor', 'middle')
+ .text(this.props.axes[1]);
};
private updateTooltip(
@@ -308,15 +348,41 @@ export class LineChart extends React.Component<LineChartProps> {
tooltip
.html(() => `<b>(${d0.x},${d0.y})</b>`) // text content for tooltip
.style('pointer-events', 'none')
- .style('transform', `translate(${xScale(d0.x) - this.width / 2}px,${yScale(d0.y) - 30}px)`);
+ .style('transform', `translate(${xScale(d0.x) - this.width}px,${yScale(d0.y)}px)`);
}
render() {
- const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none';
- return (
- <div ref={this._lineChartRef} className="chart-container">
- <span> {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Selected: ${selectedPt}`}</span>
- </div>
- );
+ this.componentDidMount();
+ var titleAccessor: any = '';
+ if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_lineChart_' + this.props.axes[0] + '-' + this.props.axes[1];
+ else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_lineChart_' + this.props.axes[0];
+ if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+ const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none';
+ if (this._lineChartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){
+ return this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? (
+ <div className="chart-container" >
+ <div className="graph-title">
+ <EditableText
+ val={StrCast(this.props.layoutDoc[titleAccessor])}
+ setVal={undoable(
+ action(val => (this.props.layoutDoc[titleAccessor] = val as string)),
+ 'Change Graph Title'
+ )}
+ color={'black'}
+ size={Size.LARGE}
+ fillWidth
+ />
+ </div>
+ <div ref={this._lineChartRef} />
+ {selectedPt != 'none' ? <div className={'selected-data'}> {`Selected: ${selectedPt}`}</div> : null}
+ </div>
+ ) : (
+ <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span>
+ );
+ } else
+ return (
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ );
}
}
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
new file mode 100644
index 000000000..213baa8a4
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -0,0 +1,399 @@
+import { observer } from 'mobx-react';
+import { Doc, StrListCast } from '../../../../../fields/Doc';
+import * as React from 'react';
+import * as d3 from 'd3';
+import { IReactionDisposer, action, computed, observable, reaction } from 'mobx';
+import { LinkManager } from '../../../../util/LinkManager';
+import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
+import { PinProps, PresBox } from '../../trails';
+import { Docs } from '../../../../documents/Documents';
+import { List } from '../../../../../fields/List';
+import './Chart.scss';
+import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
+import { FaFillDrip } from 'react-icons/fa';
+import { listSpec } from '../../../../../fields/Schema';
+import { undoable } from '../../../../util/UndoManager';
+
+export interface PieChartProps {
+ rootDoc: Doc;
+ layoutDoc: Doc;
+ axes: string[];
+ pairs: { [key: string]: any }[];
+ width: number;
+ height: number;
+ dataDoc: Doc;
+ fieldKey: string;
+ margin: {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+ };
+}
+
+@observer
+export class PieChart extends React.Component<PieChartProps> {
+ private _disposers: { [key: string]: IReactionDisposer } = {};
+ private _piechartRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _piechartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
+ private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios
+ @observable _currSelected: any | undefined = undefined; // Object of selected slice
+ private curSliceSelected: any = undefined; // d3 data of selected slice
+ private selectedData: any = undefined; // Selection of selected slice
+ private hoverOverData: any = undefined; // Selection of slice being hovered over
+
+ // filters all data to just display selected data if brushed (created from an incoming link)
+ @computed get _piechartData() {
+ var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids);
+ if (this.props.axes.length < 1) return [];
+ if (this.props.axes.length < 2) {
+ var ax0 = this.props.axes[0];
+ if (/\d/.test(this.props.pairs[0][ax0])) {
+ this.byCategory = false;
+ }
+ return this.props.pairs
+ ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
+ .map(pair => ({ [ax0]: pair[this.props.axes[0]] }));
+ }
+ var ax0 = this.props.axes[0];
+ var ax1 = this.props.axes[1];
+ if (/\d/.test(this.props.pairs[0][ax0])) {
+ this.byCategory = false;
+ }
+ return this.props.pairs
+ ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])))
+ .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] }));
+ }
+
+ @computed get defaultGraphTitle() {
+ var ax0 = this.props.axes[0];
+ var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
+ if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) {
+ return ax0 + ' Pie Chart';
+ } else return ax1 + ' by ' + ax0 + ' Pie Chart';
+ }
+
+ @computed get incomingLinks() {
+ return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+ .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link
+ .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
+ }
+
+ componentWillUnmount() {
+ Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]());
+ }
+ componentDidMount = () => {
+ this._disposers.chartData = reaction(
+ () => ({ dataSet: this._piechartData, w: this.width, h: this.height }),
+ ({ dataSet, w, h }) => {
+ if (dataSet!.length > 0) {
+ this.drawChart(dataSet, w, h);
+ }
+ },
+ { fireImmediately: true }
+ );
+ };
+
+ @action
+ restoreView = (data: Doc) => {};
+ // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
+ getAnchor = (pinProps?: PinProps) => {
+ const anchor = Docs.Create.ConfigDocument({
+ //
+ title: 'piechart doc selection' + this._currSelected,
+ });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+ return anchor;
+ };
+
+ @computed get height() {
+ return this.props.height - this.props.margin.top - this.props.margin.bottom;
+ }
+
+ @computed get width() {
+ return this.props.width - this.props.margin.left - this.props.margin.right;
+ }
+
+ // cleans data by converting numerical data to numbers and taking out empty cells
+ data = (dataSet: any) => {
+ var validData = dataSet.filter((d: { [x: string]: unknown }) => {
+ var valid = true;
+ Object.keys(dataSet[0]).map(key => {
+ if (!d[key] || Number.isNaN(d[key])) valid = false;
+ });
+ return valid;
+ });
+ var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
+ const data = validData.map((d: { [x: string]: any }) => {
+ if (!this.byCategory) {
+ return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
+ }
+ return d[field!];
+ });
+ return data;
+ };
+
+ // outlines the slice selected / hovered over
+ highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => {
+ var index = -1;
+ var sameAsCurrent: boolean;
+ const selected = svg.selectAll('.slice').filter((d: any) => {
+ index++;
+ var p1 = [0, 0]; // center of pie
+ var p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
+ var p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
+ var p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc
+
+ // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge
+ var lineCrossCount = 0;
+ // if for all 4 lines
+ if (Math.min(p1[1], p2[1]) <= pointer[1] && pointer[1] <= Math.max(p1[1], p2[1])) {
+ // within y bounds
+ if (pointer[0] <= ((pointer[1] - p1[1]) * (p2[0] - p1[0])) / (p2[1] - p1[1]) + p1[0]) lineCrossCount++;
+ } // intercepts x
+ if (Math.min(p2[1], p3[1]) <= pointer[1] && pointer[1] <= Math.max(p2[1], p3[1])) {
+ if (pointer[0] <= ((pointer[1] - p2[1]) * (p3[0] - p2[0])) / (p3[1] - p2[1]) + p2[0]) lineCrossCount++;
+ }
+ if (Math.min(p3[1], p4[1]) <= pointer[1] && pointer[1] <= Math.max(p3[1], p4[1])) {
+ if (pointer[0] <= ((pointer[1] - p3[1]) * (p4[0] - p3[0])) / (p4[1] - p3[1]) + p3[0]) lineCrossCount++;
+ }
+ if (Math.min(p4[1], p1[1]) <= pointer[1] && pointer[1] <= Math.max(p4[1], p1[1])) {
+ if (pointer[0] <= ((pointer[1] - p4[1]) * (p1[0] - p4[0])) / (p1[1] - p4[1]) + p4[0]) lineCrossCount++;
+ }
+ if (lineCrossCount % 2 != 0) {
+ // inside the slice of it crosses an odd number of edges
+ var showSelected = this.byCategory ? pieDataSet[index] : this._piechartData[index];
+ if (changeSelectedVariables) {
+ // for when a bar is selected - not just hovered over
+ sameAsCurrent = this._currSelected
+ ? showSelected[Object.keys(showSelected)[0]] == this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] == this._currSelected![Object.keys(showSelected)[1]]
+ : this._currSelected === showSelected;
+ this._currSelected = sameAsCurrent ? undefined : showSelected;
+ this.selectedData = sameAsCurrent ? undefined : d;
+ } else this.hoverOverData = d;
+ return true;
+ }
+ return false;
+ });
+ if (changeSelectedVariables) {
+ if (sameAsCurrent!) this.curSliceSelected = undefined;
+ else this.curSliceSelected = selected;
+ }
+ };
+
+ // draws the pie chart
+ drawChart = (dataSet: any, width: number, height: number) => {
+ d3.select(this._piechartRef.current).select('svg').remove();
+ d3.select(this._piechartRef.current).select('.tooltip').remove();
+
+ var percentField = Object.keys(dataSet[0])[0];
+ var descriptionField = Object.keys(dataSet[0])[1]!;
+ var radius = Math.min(width, height - this.props.margin.top - this.props.margin.bottom) / 2;
+
+ // converts data into Objects
+ var data = this.data(dataSet);
+ var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => {
+ var valid = true;
+ Object.keys(dataSet[0]).map(key => {
+ if (!d[key] || Number.isNaN(d[key])) valid = false;
+ });
+ return valid;
+ });
+ if (this.byCategory) {
+ let uniqueCategories = [...new Set(data)];
+ var pieStringDataSet: { frequency: number }[] = [];
+ for (let i = 0; i < uniqueCategories.length; i++) {
+ pieStringDataSet.push({ frequency: 0, [percentField]: uniqueCategories[i] });
+ }
+ for (let i = 0; i < data.length; i++) {
+ let sliceData = pieStringDataSet.filter((each: any) => each[percentField] == data[i]);
+ sliceData[0].frequency = sliceData[0].frequency + 1;
+ }
+ pieDataSet = pieStringDataSet;
+ percentField = Object.keys(pieDataSet[0])[0];
+ descriptionField = Object.keys(pieDataSet[0])[1]!;
+ data = this.data(pieStringDataSet);
+ }
+ var trackDuplicates: { [key: string]: any } = {};
+ data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+
+ // initial chart
+ var svg = (this._piechartSvg = d3
+ .select(this._piechartRef.current)
+ .append('svg')
+ .attr('class', 'graph')
+ .attr('width', width + this.props.margin.right + this.props.margin.left)
+ .attr('height', height + this.props.margin.top + this.props.margin.bottom)
+ .append('g'));
+ let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this.props.margin.left) + ',' + height / 2 + ')');
+ var pie = d3.pie();
+ var arc = d3.arc().innerRadius(0).outerRadius(radius);
+
+ // click/hover
+ const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet));
+ const onHover = action((e: any) => {
+ const selected = this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
+ updateHighlights();
+ });
+ const mouseOut = action((e: any) => {
+ this.hoverOverData = undefined;
+ updateHighlights();
+ });
+ const updateHighlights = () => {
+ const hoverOverSlice = this.hoverOverData;
+ const selectedData = this.selectedData;
+ svg.selectAll('path').attr('class', function (d: any) {
+ return (selectedData && d.startAngle == selectedData.startAngle && d.endAngle == selectedData.endAngle) || (hoverOverSlice && d.startAngle == hoverOverSlice.startAngle && d.endAngle == hoverOverSlice.endAngle)
+ ? 'slice hover'
+ : 'slice';
+ });
+ };
+
+ // drawing the slices
+ var selected = this.selectedData;
+ var arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
+ arcs.append('path')
+ .attr('fill', (d, i) => {
+ var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => {
+ try {
+ return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data);
+ } catch (error) {
+ return each[percentField] == d.data;
+ }
+ });
+ var dataPoint;
+ if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
+ else {
+ dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
+ trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
+ }
+ var sliceColor;
+ if (dataPoint) {
+ var accessByName = dataPoint[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
+ var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::'));
+ sliceColors.map(each => {
+ if (each[0] == StrCast(accessByName)) sliceColor = each[1];
+ });
+ }
+ return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length];
+ })
+ .attr(
+ 'class',
+ selected
+ ? function (d) {
+ return selected && d.startAngle == selected.startAngle && d.endAngle == selected.endAngle ? 'slice hover' : 'slice';
+ }
+ : function (d) {
+ return 'slice';
+ }
+ )
+ .attr('d', arc)
+ .on('click', onPointClick)
+ .on('mouseover', onHover)
+ .on('mouseout', mouseOut);
+
+ // adding labels
+ trackDuplicates = {};
+ data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+ arcs.append('text')
+ .attr('transform', function (d) {
+ var centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
+ var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
+ return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')';
+ })
+ .attr('text-anchor', 'middle')
+ .text(function (d) {
+ var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => {
+ try {
+ return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) == Number(d.data);
+ } catch (error) {
+ return each[percentField] == d.data;
+ }
+ });
+ var dataPoint;
+ if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
+ else {
+ dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
+ trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
+ }
+ return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : '';
+ });
+ };
+
+ @action changeSelectedColor = (color: string) => {
+ this.curSliceSelected.attr('fill', color);
+ var sliceName = this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '');
+
+ const sliceColors = Cast(this.props.layoutDoc.pieSliceColors, listSpec('string'), null);
+ sliceColors.map(each => {
+ if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
+ });
+ sliceColors.push(StrCast(sliceName + '::' + color));
+ };
+
+ render() {
+ this.componentDidMount();
+ var titleAccessor: any = '';
+ if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0] + '-' + this.props.axes[1];
+ else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0];
+ if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+ if (!this.props.layoutDoc.pieSliceColors) this.props.layoutDoc.pieSliceColors = new List<string>();
+ var selected: string;
+ var curSelectedSliceName = '';
+ if (this._currSelected) {
+ curSelectedSliceName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+ selected = '{ ';
+ Object.keys(this._currSelected).map(key => {
+ key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
+ });
+ selected = selected.substring(0, selected.length - 2);
+ selected += ' }';
+ } else selected = 'none';
+ var selectedSliceColor;
+ var sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::'));
+ sliceColors.map(each => {
+ if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
+ });
+
+ if (this._piechartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){
+ return this.props.axes.length >= 1 ? (
+ <div className="chart-container">
+ <div className="graph-title">
+ <EditableText
+ val={StrCast(this.props.layoutDoc[titleAccessor])}
+ setVal={undoable(
+ action(val => (this.props.layoutDoc[titleAccessor] = val as string)),
+ 'Change Graph Title'
+ )}
+ color={'black'}
+ size={Size.LARGE}
+ fillWidth
+ />
+ </div>
+ <div ref={this._piechartRef} />
+ {selected != 'none' ? (
+ <div className={'selected-data'}>
+ Selected: {selected}
+ &nbsp; &nbsp;
+ <ColorPicker
+ tooltip={'Change Slice Color'}
+ type={Type.SEC}
+ icon={<FaFillDrip />}
+ selectedColor={selectedSliceColor ? selectedSliceColor : this.curSliceSelected.attr('fill')}
+ setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
+ setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
+ size={Size.XSMALL}
+ />
+ </div>
+ ) : null}
+ </div>
+ ) : (
+ <span className="chart-container"> {'first use table view to select a column to graph'}</span>
+ );
+ } else
+ return (
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index d84e34d52..70483ac6f 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,105 +1,189 @@
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../../../../fields/Doc';
-import { Id } from '../../../../../fields/FieldSymbols';
+import { Doc, Field, StrListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
-import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../../../Utils';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
import { DragManager } from '../../../../util/DragManager';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
+import { LinkManager } from '../../../../util/LinkManager';
+import { Cast, DocCast } from '../../../../../fields/Types';
+import './Chart.scss';
+import { listSpec } from '../../../../../fields/Schema';
interface TableBoxProps {
+ rootDoc: Doc;
+ layoutDoc: Doc;
pairs: { [key: string]: any }[];
selectAxes: (axes: string[]) => void;
axes: string[];
+ width: number;
+ height: number;
+ margin: {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+ };
docView?: () => DocumentView | undefined;
}
@observer
export class TableBox extends React.Component<TableBoxProps> {
+ // filters all data to just display selected data if brushed (created from an incoming link)
+ @computed get _tableData() {
+ if (this.incomingLinks.length! <= 0) return this.props.pairs;
+ var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids);
+ return this.props.pairs?.filter(pair => this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]));
+ }
+
+ @computed get incomingLinks() {
+ return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+ .filter(link => {
+ return link.link_anchor_1 == this.props.rootDoc.draggedFrom;
+ }) // get links where this chart doc is the target of the link
+ .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
+ }
+
@computed get columns() {
- return this.props.pairs.length ? Array.from(Object.keys(this.props.pairs[0])) : [];
+ if (!this.props.layoutDoc.dataViz_rowGuids) this.props.layoutDoc.dataViz_rowGuids = new List<string>();
+ const guids = Cast(this.props.layoutDoc.dataViz_rowGuids, listSpec('string'), null);
+ if (guids.length == 0) this.props.pairs.map(row => guids.push(Utils.GenerateGuid()));
+ return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : [];
}
+
+ // updates the 'selected' field to no longer include rows that aren't in the table
+ filterSelectedRowsDown() {
+ if (!this.props.layoutDoc.dataViz_selectedRows) this.props.layoutDoc.dataViz_selectedRows = new List<string>();
+ const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null);
+ const incomingSelected = this.incomingLinks.length ? StrListCast(this.incomingLinks[0].dataViz_selectedRows) : undefined;
+ if (incomingSelected) {
+ selected.map(guid => {
+ if (!incomingSelected.includes(guid)) selected.splice(selected.indexOf(guid), 1);
+ }); // filters through selected to remove guids that were removed in the incoming data
+ }
+ }
+
render() {
- return (
- <div className="table-container">
- <table className="table">
- <thead>
- <tr className="table-row">
- {this.columns
- .filter(col => !col.startsWith('select'))
- .map(col => {
- const header = React.createRef<HTMLElement>();
+ this.filterSelectedRowsDown();
+ if (this._tableData.length > 0) {
+ return (
+ <div className="table-container" style={{ height: this.props.height }}>
+ <table className="table">
+ <thead>
+ <tr className="table-row">
+ {this.columns
+ .filter(col => !col.startsWith('select'))
+ .map(col => {
+ const header = React.createRef<HTMLElement>();
+ return (
+ <th
+ key={this.columns.indexOf(col)}
+ ref={header as any}
+ style={{
+ color: this.props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this.props.axes.lastElement() === col ? 'darkred' : undefined,
+ background: this.props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this.props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
+ fontWeight: 'bolder',
+ border: '3px solid black',
+ }}
+ onPointerDown={e => {
+ const downX = e.clientX;
+ const downY = e.clientY;
+ setupMoveUpEvents(
+ {},
+ e,
+ e => {
+ // dragging off a column to create a brushed DataVizBox
+ const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
+ const targetCreator = (annotationOn: Doc | undefined) => {
+ const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
+ embedding._dataViz = DataVizView.TABLE;
+ embedding._dataViz_axes = new List<string>([col, col]);
+ embedding._draggedFrom = this.props.docView?.()!.rootDoc!;
+ embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
+ embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
+ embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
+ embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
+ return embedding;
+ };
+ if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: e => {
+ if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+ e.linkDocument.link_displayLine = true;
+ e.linkDocument.link_matchEmbeddings = true;
+ // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+ // e.annoDragData.linkSourceDoc.followLinkZoom = false;
+ }
+ },
+ });
+ return true;
+ }
+ return false;
+ },
+ emptyFunction,
+ action(e => {
+ const newAxes = this.props.axes;
+ if (newAxes.includes(col)) {
+ newAxes.splice(newAxes.indexOf(col), 1);
+ } else if (newAxes.length > 1) {
+ newAxes[1] = col;
+ } else {
+ newAxes.push(col);
+ }
+ this.props.selectAxes(newAxes);
+ })
+ );
+ }}>
+ {col}
+ </th>
+ );
+ })}
+ </tr>
+ </thead>
+ <tbody>
+ {this._tableData?.map((p, i) => {
+ var containsData = false;
+ var guid = StrListCast(this.props.layoutDoc.dataViz_rowGuids)![this.props.pairs.indexOf(p)];
+ this.columns.map(col => {
+ if (p[col] != '' && p[col] != null && p[col] != undefined) containsData = true;
+ });
+ if (containsData) {
return (
- <th
- ref={header as any}
- style={{
- color: this.props.axes.slice().reverse().lastElement() === col ? 'green' : this.props.axes.lastElement() === col ? 'red' : undefined,
- fontWeight: this.props.axes.includes(col) ? 'bolder' : 'normal',
- }}
- onPointerDown={e => {
- const downX = e.clientX;
- const downY = e.clientY;
- setupMoveUpEvents(
- {},
- e,
- e => {
- const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
- const targetCreator = (annotationOn: Doc | undefined) => {
- const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
- embedding._dataVizView = DataVizView.LINECHART;
- embedding._data_vizAxes = new List<string>([col, col]);
- embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!;
- return embedding;
- };
- if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
- DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
- dragComplete: e => {
- if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.linkDocument.link_displayLine = true;
- // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
- // e.annoDragData.linkSourceDoc.followLinkZoom = false;
- }
- },
- });
- return true;
- }
- return false;
- },
- emptyFunction,
- action(e => {
- const newAxes = this.props.axes;
- if (newAxes.includes(col)) {
- newAxes.splice(newAxes.indexOf(col), 1);
- } else if (newAxes.length >= 1) {
- newAxes[1] = col;
- } else {
- newAxes[0] = col;
- }
- this.props.selectAxes(newAxes);
- })
+ <tr
+ key={i}
+ className="table-row"
+ onClick={action(e => {
+ // selecting a row
+ const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null);
+ if (selected.includes(guid)) selected.splice(selected.indexOf(guid), 1);
+ else {
+ selected.push(guid);
+ }
+ })}
+ style={{ background: StrListCast(this.props.layoutDoc.dataViz_selectedRows).includes(guid) ? 'lightgrey' : '', width: '110%' }}>
+ {this.columns.map(col => {
+ // each cell
+ var colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false;
+ return (
+ <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
+ {p[col]}
+ </td>
);
- }}>
- {col}
- </th>
+ })}
+ </tr>
);
- })}
- </tr>
- </thead>
- <tbody>
- {this.props.pairs?.map((p, i) => {
- return (
- <tr className="table-row" onClick={action(e => (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}>
- {this.columns.map(col => (
- <td style={{ fontWeight: p['select' + this.props.docView?.()?.rootDoc![Id]] ? 'bold' : '' }}>{p[col]}</td>
- ))}
- </tr>
- );
- })}
- </tbody>
- </table>
- </div>
- );
+ }
+ })}
+ </tbody>
+ </table>
+ </div>
+ );
+ } else
+ return (
+ // when it is a brushed table and the incoming table doesn't have any rows selected
+ <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+ );
}
}
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index e1ff6f8eb..10bfb0c64 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -34,7 +34,6 @@ export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never
};
export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => {
- console.log('x axis creator being called');
g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15));
};
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e954d0484..2d8663c9c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -18,7 +18,6 @@ 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';
@@ -26,9 +25,11 @@ import { DocumentViewProps } from './DocumentView';
import './DocumentView.scss';
import { EquationBox } from './EquationBox';
import { FieldView, FieldViewProps } from './FieldView';
+import { FontIconBox } from './FontIconBox/FontIconBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { FunctionPlotBox } from './FunctionPlotBox';
import { ImageBox } from './ImageBox';
+import { ImportElementBox } from './importBox/ImportElementBox';
import { KeyValueBox } from './KeyValueBox';
import { LabelBox } from './LabelBox';
import { LinkAnchorBox } from './LinkAnchorBox';
@@ -269,6 +270,7 @@ export class DocumentContentsView extends React.Component<
LoadingBox,
PhysicsSimulationBox,
SchemaRowBox,
+ ImportElementBox,
}}
bindings={bindings}
jsx={layoutFrame}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 1265651ad..b25540dd3 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -24,7 +24,7 @@
width: 100%;
height: 100%;
border-radius: inherit;
- transition: outline 0.3s linear;
+ // transition: outline 0.3s linear;
// background: $white; //overflow: hidden;
transform-origin: center;
@@ -115,7 +115,10 @@
width: 100%;
height: 100%;
transition: inherit;
-
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
.sharingIndicator {
height: 30px;
width: 30px;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 461066b1b..39c8d3348 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -51,6 +51,7 @@ import { LinkAnchorBox } from './LinkAnchorBox';
import { PresEffect, PresEffectDirection } from './trails';
import { PinProps, PresBox } from './trails/PresBox';
import React = require('react');
+import { SettingsManager } from '../../util/SettingsManager';
const { Howl } = require('howler');
interface Window {
@@ -68,9 +69,7 @@ export enum OpenWhere {
addLeft = 'add:left',
addRight = 'add:right',
addBottom = 'add:bottom',
- dashboard = 'dashboard',
close = 'close',
- fullScreen = 'fullScreen',
toggle = 'toggle',
toggleRight = 'toggle:right',
replace = 'replace',
@@ -114,7 +113,7 @@ export interface DocComponentView {
getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => 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)
restoreView?: (viewSpec: Doc) => boolean;
scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
- brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void;
+ brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void;
getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
@@ -160,9 +159,11 @@ export interface DocumentViewSharedProps {
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
PanelHeight: () => number;
+ shouldNotScale?: () => boolean;
docViewPath: () => DocumentView[];
childHideDecorationTitle?: () => boolean;
childHideResizeHandles?: () => boolean;
+ childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
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>;
setTitleFocus?: () => void;
@@ -182,7 +183,7 @@ export interface DocumentViewSharedProps {
pinToPres: (document: Doc, pinProps: PinProps) => void;
ScreenToLocalTransform: () => Transform;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
- canEmbedOnDrag?: boolean;
+ dragAction?: dropActionType;
treeViewDoc?: Doc;
xPadding?: number;
yPadding?: number;
@@ -194,7 +195,6 @@ export interface DocumentViewSharedProps {
forceAutoHeight?: boolean;
disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
- enableDragWhenActive?: boolean;
waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
pointerEvents?: () => Opt<string>;
@@ -332,8 +332,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return (
DocumentView.LongPress ||
onScriptDisable === 'always' ||
- (onScriptDisable !== 'never' && (this.rootSelected() || this.props.isSelected())) ||
- this._componentView?.isAnyChildContentActive?.()
+ (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))
);
}
@computed get onClickHandler() {
@@ -355,11 +354,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
componentDidMount() {
this.setupHandlers();
}
-
+ preDropFunc = (e: Event, de: DragManager.DropEvent) => {
+ const dropAction = this.layoutDoc.dropAction as dropActionType;
+ if (de.complete.docDragData && this.isContentActive() && !this.props.treeViewDoc) {
+ dropAction && (de.complete.docDragData.dropAction = dropAction);
+ e.stopPropagation();
+ }
+ };
setupHandlers() {
this.cleanupHandlers(false);
if (this._mainCont.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document, this.preDropFunc);
this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
}
@@ -387,7 +392,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
dragData.treeViewDoc = this.props.treeViewDoc;
dragData.removeDocument = this.props.removeDocument;
dragData.moveDocument = this.props.moveDocument;
- dragData.canEmbed = this.props.canEmbedOnDrag;
+ dragData.canEmbed = this.rootDoc.dragAction ?? this.props.dragAction ? true : false;
const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
DragManager.StartDocumentDrag(
@@ -478,7 +483,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
: '';
DocumentViewInternal.addDocTabFunc = oldFunc;
};
- clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title));
+ clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title);
} else {
// onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
@@ -486,8 +491,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
preventDefault = false;
}
-
- this._singleClickFunc = clickFunc ?? (() => this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey));
+ const sendToBack = e.altKey;
+ this._singleClickFunc =
+ clickFunc ??
+ (() =>
+ sendToBack
+ ? this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.bringToFront(this.rootDoc, true)
+ : this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey));
const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
@@ -506,7 +516,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onPointerDown = (e: React.PointerEvent): void => {
this._longPressSelector = setTimeout(() => {
if (DocumentView.LongPress) {
- if (this.rootDoc.dontUndo) {
+ if (this.rootDoc.undoIgnoreFields) {
runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline));
} else {
this.props.select(false);
@@ -523,7 +533,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// if this is part of a template, let the event go up to the template root unless right/ctrl clicking
if (
// prettier-ignore
- this.props.isDocumentActive?.() &&
+ (this.props.isDocumentActive?.() || this.props.isContentActive?.()) &&
!this.props.onBrowseClick?.() &&
!this.Document.ignoreClick &&
e.button === 0 &&
@@ -535,7 +545,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
//if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
// listen to move events if document content isn't active or document is draggable
- if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.enableDragWhenActive || this.rootDoc.enableDragWhenActive)) {
+ if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive))) {
document.addEventListener('pointermove', this.onPointerMove);
}
}
@@ -549,7 +559,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
this.cleanupPointerEvents();
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
+ this._longPressSelector && clearTimeout(this._longPressSelector);
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this.props.dragAction || undefined) as dropActionType));
}
};
@@ -567,8 +578,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.onPointerUpHandler?.script) {
this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
} else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
- this._doubleTap = this.rootDoc.defaultDoubleClick !== 'ignore' && Date.now() - this._lastTap < Utils.CLICK_TIME;
- if (!this.props.isSelected(true)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
+ this._doubleTap = (this.onDoubleClickHandler?.script || this.rootDoc.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
+ if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
}
if (DocumentView.LongPress) e.preventDefault();
};
@@ -608,16 +619,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
+ if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
if (this.props.Document === Doc.ActiveDashboard) {
+ e.stopPropagation();
+ e.preventDefault();
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;
+ return true;
}
const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
if (linkdrag) {
linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
if (linkdrag.linkSourceDoc) {
- e.stopPropagation();
if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
}
@@ -625,8 +637,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
}
+ e.stopPropagation();
+ return true;
}
}
+ return false;
};
@undoBatch
@@ -712,6 +727,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layout_fieldKey)], Doc, null);
const appearance = cm.findByDescription('UI Controls...');
const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
+
+ if (this.props.renderDepth === 0) {
+ appearanceItems.push({ description: 'Open in Lightbox', event: () => LightboxView.SetLightboxDoc(this.rootDoc), icon: 'hand-point-right' });
+ }
!Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
!appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
@@ -779,17 +798,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
!more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
}
const constantItems: ContextMenuProps[] = [];
- constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
if (!Doc.IsSystem(this.rootDoc)) {
- constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
- constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() });
+ constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
(this.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
if (this.props.removeDocument && 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)
constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
}
}
- cm.addItem({ description: 'General...', noexpand: !Doc.IsSystem(this.rootDoc), subitems: constantItems, icon: 'question' });
+ constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
+ cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
const help = cm.findByDescription('Help...');
const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
@@ -828,6 +846,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
documentationDescription = 'See text node documentation';
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/text/';
break;
+ case DocumentType.DATAVIZ:
+ documentationDescription = 'See DataViz node documentation';
+ documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/';
+ break;
}
// Add link to help documentation
if (!this.props.treeViewDoc && documentationDescription && documentationLink) {
@@ -844,44 +866,50 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
};
- rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
+ @computed get _rootSelected() {
+ return this.props.isSelected(false) || (this.props.Document.rootDocument && this.props.rootSelected?.(false)) || false;
+ }
+ rootSelected = (outsideReaction?: boolean) => this._rootSelected;
panelHeight = () => this.props.PanelHeight() - this.headerMargin;
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
setHeight = (height: number) => (this.layoutDoc._height = height);
setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
- isContentActive = (outsideReaction?: boolean): boolean | undefined => {
- return this.props.isContentActive() === false
+ @computed get _isContentActive() {
+ // true - if the document has been activated directly or indirectly (by having its children selected)
+ // false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
+ // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking)
+ return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
? false
- : Doc.ActiveTool !== InkTool.None ||
- SnappingManager.GetIsDragging() ||
- this.rootSelected() ||
- this.rootDoc.forceActive ||
- this.props.isSelected(outsideReaction) ||
- this._componentView?.isAnyChildContentActive?.() ||
- this.props.isContentActive()
+ : Doc.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
? true
: undefined;
- };
+ }
+ isContentActive = (): boolean | undefined => this._isContentActive;
@observable _retryThumb = 1;
- thumbShown = () => {
- const childHighlighted = () =>
- Array.from(Doc.highlightedDocs.keys())
- .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
- .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
+ @computed get _thumbShown() {
+ const childHighlighted = () => false;
+ // Array.from(Doc.highlightedDocs.keys())
+ // .concat(Array.from(Doc.brushManager.BrushedDoc.keys()))
+ // .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc));
const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc));
return !this.props.LayoutTemplateString &&
- !this.props.isSelected() &&
+ !this.isContentActive() &&
LightboxView.LightboxDoc !== this.rootDoc &&
this.thumb &&
!Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
- ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking) &&
- !this._componentView?.isAnyChildContentActive?.()
+ ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking)
? true
: false;
- };
+ }
+ thumbShown = () => this._thumbShown;
childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
- contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents);
+
+ /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
+ @computed get _contentPointerEvents() {
+ return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents;
+ }
+ contentPointerEvents = () => this._contentPointerEvents;
@computed get contents() {
TraceMobx();
const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString;
@@ -889,7 +917,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
<div
className="documentView-contentsView"
style={{
- pointerEvents: (isInk ? 'none' : this.pointerEvents) ?? 'all',
+ pointerEvents: (isInk ? 'none' : this.contentPointerEvents()) ?? 'all',
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
{!this._retryThumb || !this.thumbShown() ? null : (
@@ -950,8 +978,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
TraceMobx();
return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(
link =>
- Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.rootDoc) ||
- Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.rootDoc) ||
+ (link.link_matchEmbeddings ? link.link_anchor_1 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.rootDoc)) ||
+ (link.link_matchEmbeddings ? link.link_anchor_2 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.rootDoc)) ||
((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.rootDoc)) ||
((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.rootDoc))
);
@@ -997,9 +1025,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
audio: true,
})
.then(function (stream) {
- let audioTextAnnos = Cast(dataDoc[field + '-audioAnnotations-text'], listSpec('string'), null);
+ let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
if (audioTextAnnos) audioTextAnnos.push('');
- else audioTextAnnos = dataDoc[field + '-audioAnnotations-text'] = new List<string>(['']);
+ else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
DictationManager.Controls.listen({
interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
continuous: { indefinite: false },
@@ -1016,9 +1044,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
if (!(result instanceof Error)) {
const audioField = new AudioField(result.accessPaths.agnostic.client);
- const audioAnnos = Cast(dataDoc[field + '-audioAnnotations'], listSpec(AudioField), null);
+ const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
if (audioAnnos === undefined) {
- dataDoc[field + '-audioAnnotations'] = new List([audioField]);
+ dataDoc[field + '_audioAnnotations'] = new List([audioField]);
} else {
audioAnnos.push(audioField);
}
@@ -1039,7 +1067,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
playAnnotation = () => {
const self = this;
const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
- const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
+ const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
const anno = audioAnnos?.lastElement();
if (anno instanceof AudioField && audioAnnoState === 'stopped') {
new Howl({
@@ -1058,15 +1086,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get innards() {
TraceMobx();
const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
- const layout_showTitle = this.layout_showTitle?.split(':')[0];
- const layout_showTitleHover = this.layout_showTitle?.includes(':hover');
+ const showTitle = this.layout_showTitle?.split(':')[0];
+ const showTitleHover = this.layout_showTitle?.includes(':hover');
const captionView = !this.layout_showCaption ? null : (
<div
className="documentView-captionWrapper"
style={{
- pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+ pointerEvents: this.rootDoc.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
minWidth: 50 * ffscale(),
maxHeight: `max(100%, ${20 * ffscale()}px)`,
+ background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
+ color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
}}>
<FormattedTextBox
{...this.props}
@@ -1083,27 +1113,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
/>
</div>
);
- const targetDoc = layout_showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
- const background = StrCast(
- SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor,
- Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : 'rgba(0,0,0,0.4)'
- );
- const layout_sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
- const titleView = !layout_showTitle ? null : (
+ const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
+ const background = StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.Instance.userVariantColor));
+ const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
+ const titleView = !showTitle ? null : (
<div
- className={`documentView-titleWrapper${layout_showTitleHover ? '-hover' : ''}`}
+ className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
key="title"
style={{
position: this.headerMargin ? 'relative' : 'absolute',
height: this.titleHeight,
- width: !this.headerMargin ? `calc(${layout_sidebarWidthPercent || 100}% - 18px)` : (layout_sidebarWidthPercent || 100) + '%', // leave room for annotation button
+ width: !this.headerMargin ? `calc(${sidebarWidthPercent || 100}% - 18px)` : (sidebarWidthPercent || 100) + '%', // leave room for annotation button
color: lightOrDark(background),
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
}}>
<EditableView
ref={this._titleRef}
- contents={layout_showTitle
+ contents={showTitle
.split(';')
.map(field => field.trim())
.map(field => targetDoc[field]?.toString())
@@ -1112,7 +1139,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
fontSize={10}
GetValue={() => {
this.props.select(false);
- return layout_showTitle.split(';').length === 1 ? layout_showTitle + '=' + Field.toString(targetDoc[layout_showTitle.split(';')[0]] as any as Field) : '#' + layout_showTitle;
+ return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle;
}}
SetValue={undoBatch((input: string) => {
if (input?.startsWith('#')) {
@@ -1122,17 +1149,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date';
}
} else {
- var value = input.replace(new RegExp(layout_showTitle + '='), '') as string | number;
- if (layout_showTitle !== 'title' && Number(value).toString() === value) value = Number(value);
- if (layout_showTitle.includes('Date') || layout_showTitle === 'author') return true;
- Doc.SetInPlace(targetDoc, layout_showTitle, value, 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 || (!layout_showTitle && !this.layout_showCaption) ? (
+ return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
this.contents
) : (
<div className="documentView-styleWrapper">
@@ -1176,7 +1203,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
* @returns a function that will wrap a JSX animation element wrapping any JSX element
*/
public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
- const dir = presEffectDoc?.presEffectDirection ?? presEffectDoc?.followLinkAnimDirection;
+ const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
const effectProps = {
left: dir === PresEffectDirection.Left,
right: dir === PresEffectDirection.Right,
@@ -1184,10 +1211,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
bottom: dir === PresEffectDirection.Bottom,
opposite: true,
delay: 0,
- duration: Cast(presEffectDoc?.presTransition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
+ duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
};
//prettier-ignore
- switch (StrCast(presEffectDoc?.presEffect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
+ switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
default:
case PresEffect.None: return renderDoc;
case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
@@ -1199,16 +1226,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
}
}
+ @computed get highlighting() {
+ return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
+ }
+ @computed get borderPath() {
+ return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath);
+ }
render() {
TraceMobx();
- const highlighting = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting);
- const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath);
+ const highlighting = this.highlighting;
+ const borderPath = this.borderPath;
const boxShadow =
this.props.treeViewDoc || !highlighting
? this.boxShadow
: highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
- : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+ : this.boxShadow || (this.rootDoc.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
const renderDoc = this.renderDoc({
borderRadius: this.borderRounding,
outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1224,9 +1257,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
- onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
- onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
- onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.props.Document)}
+ onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+ onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)}
+ onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)}
style={{
borderRadius: this.borderRounding,
pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents,
@@ -1326,7 +1359,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return this.docView?._componentView;
}
get allLinks() {
- return this.docView?.allLinks || [];
+ return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.rootDoc || link.link_anchor_2 === this.rootDoc);
}
get LayoutFieldKey() {
return this.docView?.LayoutFieldKey || 'layout';
@@ -1357,7 +1390,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.layout_fitWidth));
}
@computed get shouldNotScale() {
- return (this.layout_fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
+ return this.props.shouldNotScale?.() || (this.layout_fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
}
@computed get effectiveNativeWidth() {
return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
@@ -1509,7 +1542,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
<div className="webBox-textHighlight">
<ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
</div>,
- { presEffect: this.htmlOverlayEffect ?? 'Zoom' } as any as Doc,
+ { presentation_effect: this.htmlOverlayEffect ?? 'Zoom' } as any as Doc,
this.rootDoc
)}{' '}
</Fade>
@@ -1580,7 +1613,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
let wid = linkSource[Width]();
- let embedding:Doc|undefined;
+ let embedding: Doc | undefined;
const links = LinkManager.Links(linkSource);
links.forEach(link => {
const other = LinkManager.getOppositeAnchor(link, linkSource);
@@ -1594,6 +1627,6 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
}
});
- embedding && DocServer.UPDATE_SERVER_CACHE();// if a new embedding was made, update the client's server cache so that it will not come back as a promise
+ embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
return links;
});
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index 9714e1bd0..f5871db22 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -1,8 +1,9 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.scss';
.equationBox-cont {
- transform-origin: top left;
+ transform-origin: center;
+ background-color: #e7e7e7;
> span {
- width: 100%;
+ 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 8d45c5724..a77e4bdd1 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -26,7 +26,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.props.select(false);
this._ref.current!.mathField.focus();
- this._ref.current!.mathField.select();
+ this.rootDoc.text === 'x' && this._ref.current!.mathField.select();
+ EquationBox.SelectOnLoad = '';
}
reaction(
() => StrCast(this.dataDoc.text),
@@ -35,6 +36,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._ref.current!.mathField.latex(text);
}
}
+ //{ fireImmediately: true }
);
reaction(
() => this.props.isSelected(),
@@ -53,9 +55,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
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({
+ const nextEq = Docs.Create.EquationDocument(e.shiftKey ? StrCast(this.dataDoc.text) : 'x', {
title: '# math',
- text: StrCast(this.dataDoc.text),
_width,
_height: 25,
x: NumCast(this.layoutDoc.x),
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 85dd779fc..4ebf22ddf 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -45,7 +45,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
diff --git a/src/client/views/nodes/button/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
index 0aa2ac8e1..0aa2ac8e1 100644
--- a/src/client/views/nodes/button/ButtonInterface.ts
+++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
diff --git a/src/client/views/nodes/button/FontIconBadge.scss b/src/client/views/nodes/FontIconBox/FontIconBadge.scss
index 78f506e57..2ff5c651f 100644
--- a/src/client/views/nodes/button/FontIconBadge.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBadge.scss
@@ -1,11 +1,12 @@
.fontIconBadge {
- background: red;
+ background: lightgreen;
width: 15px;
height: 15px;
top: 8px;
+ color: black;
display: block;
position: absolute;
right: 5;
border-radius: 50%;
text-align: center;
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/FontIconBox/FontIconBadge.tsx
index b50588ce2..b50588ce2 100644
--- a/src/client/views/nodes/button/FontIconBadge.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBadge.tsx
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index f3b43501b..9d9fa26b0 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -18,7 +18,7 @@
.fontIconBox-label {
color: $white;
- bottom: 0;
+ bottom: -1;
position: absolute;
text-align: center;
font-size: 7px;
@@ -27,7 +27,7 @@
border-radius: 8px;
padding: 0;
width: 100%;
- font-family: 'ROBOTO';
+ font-family: 'system-ui';
text-transform: uppercase;
font-weight: bold;
transition: 0.15s;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
new file mode 100644
index 000000000..5ff5f7bfa
--- /dev/null
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -0,0 +1,409 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, MultiToggle, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
+import { ScriptField } from '../../../../fields/ScriptField';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { undoable, UndoManager } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
+import { DocComponent } from '../../DocComponent';
+import { EditableView } from '../../EditableView';
+import { Colors } from '../../global/globalEnums';
+import { StyleProp } from '../../StyleProvider';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { OpenWhere } from '../DocumentView';
+import { RichTextMenu } from '../formattedText/RichTextMenu';
+import './FontIconBox.scss';
+import { SelectedDocView } from '../../selectedDoc';
+import { Utils } from '../../../../Utils';
+import { FaAlignCenter, FaAlignJustify, FaAlignLeft, FaAlignRight } from 'react-icons/fa';
+
+export enum ButtonType {
+ TextButton = 'textBtn',
+ MenuButton = 'menuBtn',
+ DropdownList = 'dropdownList',
+ DropdownButton = 'dropdownBtn',
+ ClickButton = 'clickBtn',
+ ToggleButton = 'toggleBtn',
+ ColorButton = 'colorBtn',
+ ToolButton = 'toolBtn',
+ MultiToggleButton = 'multiToggleBtn',
+ NumberSliderButton = 'numSliderBtn',
+ NumberDropdownButton = 'numDropdownBtn',
+ NumberInlineButton = 'numInlineBtn',
+ EditableText = 'editableText',
+}
+
+export interface ButtonProps extends FieldViewProps {
+ type?: ButtonType;
+}
+@observer
+export class FontIconBox extends DocComponent<ButtonProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(FontIconBox, fieldKey);
+ }
+ @observable noTooltip = false;
+ showTemplate = (): void => {
+ const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
+ dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight);
+ };
+ 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.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' });
+ }
+ };
+
+ static GetShowLabels() {
+ return BoolCast(Doc.UserDoc()._showLabel);
+ }
+ static SetShowLabels(show: boolean) {
+ Doc.UserDoc()._showLabel = show;
+ }
+ static GetRecognizeGestures() {
+ return BoolCast(Doc.UserDoc()._recognizeGestures);
+ }
+ static SetRecognizeGestures(show: boolean) {
+ Doc.UserDoc()._recognizeGestures = show;
+ }
+
+ // Determining UI Specs
+ @computed get label() {
+ return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title));
+ }
+ Icon = (color: string, iconFalse?: boolean) => {
+ let icon;
+ if (iconFalse) {
+ icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as any;
+ if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
+ else return null;
+ }
+ icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
+ const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />;
+ return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
+ };
+ @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:
+ * - Main menu button (LHS)
+ * - Tool button
+ * - Expandable button (CollectionLinearView)
+ * - Button inside of CollectionLinearView vs. outside of CollectionLinearView
+ * - Action button
+ * - Dropdown button
+ * - Color button
+ * - Dropdown list
+ * - Number button
+ **/
+
+ _batch: UndoManager.Batch | undefined = undefined;
+ /**
+ * Number button
+ */
+ @computed get numberDropdown() {
+ let type: NumberDropdownType;
+ switch (this.type) {
+ case ButtonType.NumberDropdownButton:
+ type = 'dropdown';
+ break;
+ case ButtonType.NumberInlineButton:
+ type = 'input';
+ break;
+ case ButtonType.NumberSliderButton:
+ default:
+ type = 'slider';
+ break;
+ }
+ const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: value === undefined });
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ // Script for checking the outcome of the toggle
+ const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
+ const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
+
+ return (
+ <NumberDropdown
+ color={color}
+ numberDropdownType={type}
+ showPlusMinus={false}
+ tooltip={this.label}
+ type={Type.PRIM}
+ min={NumCast(this.rootDoc.numBtnMin, 0)}
+ max={NumCast(this.rootDoc.numBtnMax, 100)}
+ number={checkResult}
+ setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}
+ fillWidth
+ />
+ );
+ }
+
+ /**
+ * Dropdown button
+ */
+ @computed get dropdownButton() {
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ 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}`}
+ style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
+ onClick={action(() => {
+ this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
+ this.noTooltip = this.rootDoc.dropDownOpen;
+ Doc.UnBrushAllDocs();
+ })}>
+ {this.Icon(color)}
+ {!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}
+ </div>
+ );
+ }
+
+ /**
+ * Dropdown list
+ */
+ @computed get dropdownListButton() {
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+ const script = ScriptCast(this.rootDoc.script);
+
+ let noviceList: string[] = [];
+ let text: string | undefined;
+ let dropdown = true;
+ let getStyle: (val: string) => any = () => {};
+ let icon: IconProp = 'caret-down';
+ let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView');
+ try {
+ if (isViewDropdown) {
+ const selectedDocs: Doc[] = SelectionManager.Docs();
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected) {
+ if (StrCast(selected.type) === DocumentType.COL) {
+ text = StrCast(selected._type_collection);
+ } else {
+ dropdown = false;
+ if (selectedDocs.length > 1) {
+ text = selectedDocs.length + ' selected';
+ } else {
+ text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType);
+ icon = Doc.toIcon(selected);
+ }
+ return <Popup icon={<FontAwesomeIcon size={'1x'} icon={icon} />} text={text} type={Type.TERT} color={color} popup={<SelectedDocView selectedDocs={selectedDocs} />} fillWidth />;
+ }
+ } else {
+ dropdown = false;
+ return <Button text={`None Selected`} type={Type.TERT} color={color} fillWidth inactive />;
+ }
+ noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
+ } else {
+ text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+ getStyle = (val: string) => {
+ return { fontFamily: val };
+ };
+ }
+ } catch (e) {
+ console.log(e);
+ }
+
+ // Get items to place into the list
+ const list: IListItemProps[] = this.buttonList
+ .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
+ .map(value => ({
+ text: value.charAt(0).toUpperCase() + value.slice(1),
+ val: value,
+ style: getStyle(value),
+ onClick: undoable(() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), value),
+ // shortcut: '#',
+ }));
+
+ return (
+ <Dropdown
+ selectedVal={text}
+ setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)}
+ color={color}
+ type={isViewDropdown ? Type.TERT : Type.PRIM}
+ dropdownType={DropdownType.SELECT}
+ items={list}
+ tooltip={this.label}
+ fillWidth
+ />
+ );
+ }
+
+ @computed get colorScript() {
+ return ScriptCast(this.rootDoc.script);
+ }
+
+ colorBatch: UndoManager.Batch | undefined;
+ /**
+ * Color button
+ */
+ @computed get colorButton() {
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const curColor = this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';
+ const tooltip: string = StrCast(this.rootDoc.toolTip);
+
+ return (
+ <ColorPicker
+ setSelectedColor={value => {
+ if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
+ this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false });
+ }}
+ setFinalColor={value => {
+ this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false });
+ this.colorBatch?.end();
+ this.colorBatch = undefined;
+ }}
+ defaultPickerType="Classic"
+ selectedColor={curColor}
+ type={Type.PRIM}
+ color={color}
+ icon={this.Icon(color)!}
+ tooltip={tooltip}
+ label={this.label}
+ />
+ );
+ }
+ @computed get multiToggleButton() {
+ // Determine the type of toggle button
+ const tooltip: string = StrCast(this.rootDoc.toolTip);
+
+ const script = ScriptCast(this.rootDoc.onClick);
+ const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+ // Colors
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const items = DocListCast(this.rootDoc.data);
+ return (
+ <MultiToggle
+ tooltip={`Toggle ${tooltip}`}
+ type={Type.PRIM}
+ color={color}
+ label={this.label}
+ items={DocListCast(this.rootDoc.data).map(item => ({
+ icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as any} color={color} />,
+ tooltip: StrCast(item.toolTip),
+ val: StrCast(item.toolType),
+ }))}
+ selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)}
+ setSelectedVal={(val: string | number) => {
+ const itemDoc = items.find(item => item.toolType === val);
+ itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false });
+ }}
+ />
+ );
+ }
+
+ @computed get toggleButton() {
+ // Determine the type of toggle button
+ const buttonText: string = StrCast(this.rootDoc.buttonText);
+ const tooltip: string = StrCast(this.rootDoc.toolTip);
+
+ const script = ScriptCast(this.rootDoc.onClick);
+ const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+ // Colors
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+ return (
+ <Toggle
+ tooltip={`Toggle ${tooltip}`}
+ toggleType={ToggleType.BUTTON}
+ type={Type.PRIM}
+ toggleStatus={toggleStatus}
+ text={buttonText}
+ color={color}
+ icon={this.Icon(color)!}
+ label={this.label}
+ onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })}
+ />
+ );
+ }
+
+ /**
+ * Default
+ */
+ @computed get defaultButton() {
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const tooltip: string = StrCast(this.rootDoc.toolTip);
+
+ return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />;
+ }
+
+ @computed get editableText() {
+ // Script for running the toggle
+ const script = ScriptCast(this.rootDoc.script);
+ // Function to run the script
+ const checkResult = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+
+ const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: false }).result;
+
+ return <EditableText editing={false} setEditing={(editing: boolean) => {}} />;
+
+ 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({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const tooltip = StrCast(this.rootDoc.toolTip);
+ const scriptFunc = () => ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false });
+ const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label };
+ // prettier-ignore
+ switch (this.type) {
+ case ButtonType.NumberDropdownButton:
+ case ButtonType.NumberInlineButton:
+ case ButtonType.NumberSliderButton: return this.numberDropdown;
+ case ButtonType.EditableText: return this.editableText;
+ case ButtonType.DropdownList: return this.dropdownListButton;
+ case ButtonType.ColorButton: return this.colorButton;
+ case ButtonType.DropdownButton: return this.dropdownButton;
+ case ButtonType.MultiToggleButton: return this.multiToggleButton;
+ case ButtonType.ToggleButton: return this.toggleButton;
+ case ButtonType.ClickButton:
+ case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
+ case ButtonType.TextButton: return <Button {...btnProps} text={StrCast(this.rootDoc.buttonText)}/>;
+ case ButtonType.MenuButton: return <IconButton {...btnProps} color={color} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />;
+ }
+ return this.defaultButton;
+ }
+}
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 1a78583f9..40f48dafe 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -43,13 +43,13 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
);
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const anchor = Docs.Create.FunctionPlotConfigDocument({
+ const anchor = Docs.Create.ConfigDocument({
//
annotationOn: this.rootDoc,
});
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), datarange: true } }, this.rootDoc);
- anchor.presXRange = new List<number>(Array.from(this._plot.options.xAxis.domain));
- anchor.presYRange = new List<number>(Array.from(this._plot.options.yAxis.domain));
+ anchor.config_xRange = new List<number>(Array.from(this._plot.options.xAxis.domain));
+ anchor.config_yRange = new List<number>(Array.from(this._plot.options.yAxis.domain));
if (addAsAnnotation) this.addDocument(anchor);
return anchor;
};
@@ -80,9 +80,10 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData?.droppedDocuments.length) {
+ const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc), true);
+ !added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
- de.complete.docDragData.droppedDocuments.map(doc => Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc));
- return false;
+ return added;
}
return false;
};
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b302e7ce..44da98f75 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -22,7 +22,6 @@ import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
-import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../../views/ContextMenu';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -78,12 +77,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const anchor =
this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
- Docs.Create.ImageConfigDocument({
+ Docs.Create.ConfigDocument({
title: 'ImgAnchor:' + this.rootDoc.title,
- presPanX: NumCast(this.layoutDoc._freeform_panX),
- presPanY: NumCast(this.layoutDoc._freeform_panY),
- presViewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
- presTransition: 1000,
+ config_panX: NumCast(this.layoutDoc._freeform_panX),
+ config_panY: NumCast(this.layoutDoc._freeform_panY),
+ config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
annotationOn: this.rootDoc,
});
if (anchor) {
@@ -135,19 +133,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
+ let added: boolean | undefined = undefined;
const targetIsBullseye = (ele: HTMLElement): boolean => {
if (!ele) return false;
if (ele === this._overlayIconRef.current) return true;
return targetIsBullseye(ele.parentElement as HTMLElement);
};
if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
- de.complete.docDragData.droppedDocuments.forEach(
- action((drop: Doc) => {
- Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
- this.rootDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
- e.stopPropagation();
- })
- );
+ added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
+ this.rootDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
+ return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+ }, true);
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
const targetField = Doc.LayoutFieldKey(layoutDoc);
@@ -156,10 +152,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey);
- e.stopPropagation();
}
}
+ added === false && e.preventDefault();
+ added !== undefined && e.stopPropagation();
+ return added;
}
+ return false;
};
@undoBatch
@@ -169,14 +168,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
setNativeSize = action(() => {
const scaling = (this.props.DocumentView?.().props.ScreenToLocalTransform().Scale || 1) / NumCast(this.rootDoc._freeform_scale, 1);
const nscale = NumCast(this.props.PanelWidth()) / scaling;
- const nh = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']);
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
- this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nh;
- this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nh;
- this.rootDoc._freeform_panX = nh * NumCast(this.rootDoc._freeform_panX);
- this.rootDoc._freeform_panY = nh * NumCast(this.rootDoc._freeform_panY);
- this.dataDoc._freeform_panXMax = this.dataDoc._freeform_panXMax ? nh * NumCast(this.dataDoc._freeform_panXMax) : undefined;
- this.dataDoc._freeform_panXMin = this.dataDoc._freeform_panXMin ? nh * NumCast(this.dataDoc._freeform_panXMin) : undefined;
+ this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
+ this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
+ this.rootDoc._freeform_panX = nw * NumCast(this.rootDoc._freeform_panX);
+ this.rootDoc._freeform_panY = nw * NumCast(this.rootDoc._freeform_panY);
+ this.dataDoc._freeform_panXMax = this.dataDoc._freeform_panXMax ? nw * NumCast(this.dataDoc._freeform_panXMax) : undefined;
+ this.dataDoc._freeform_panXMin = this.dataDoc._freeform_panXMin ? nw * NumCast(this.dataDoc._freeform_panXMin) : undefined;
this.dataDoc._freeform_panYMax = this.dataDoc._freeform_panYMax ? nw * NumCast(this.dataDoc._freeform_panYMax) : undefined;
this.dataDoc._freeform_panYMin = this.dataDoc._freeform_panYMin ? nw * NumCast(this.dataDoc._freeform_panYMin) : undefined;
});
@@ -295,7 +293,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
choosePath(url: URL) {
const lower = url.href.toLowerCase();
if (url.protocol === 'data') return url.href;
- if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href);
+ if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
const ext = extname(url.href);
@@ -387,7 +385,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ref={this._overlayIconRef}
onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
style={{
- display: (SnappingManager.GetIsDragging() && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+ display: (this.props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
width: 'min(10%, 25px)',
height: 'min(10%, 25px)',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -414,7 +412,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get content() {
TraceMobx();
- const backAlpha = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor)).alpha();
+ const backColor = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor));
+ const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize;
@@ -505,7 +504,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
})}
style={{
- display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
+ display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
width: this.props.PanelWidth() ? undefined : `100%`,
height: this.props.PanelWidth() ? undefined : `100%`,
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 5b6b0b5a7..673f711be 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -4,7 +4,7 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { DocCast, NumCast } from '../../../fields/Types';
+import { DocCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { returnAll, returnAlways, returnTrue } from '../../../Utils';
import { Docs } from '../../documents/Documents';
@@ -13,6 +13,7 @@ import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripti
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
+import { DocumentIconContainer } from './DocumentIcon';
import { OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
@@ -20,10 +21,6 @@ import { ImageBox } from './ImageBox';
import './KeyValueBox.scss';
import { KeyValuePair } from './KeyValuePair';
import React = require('react');
-import { DocumentManager } from '../../util/DocumentManager';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { ScriptingRepl } from '../ScriptingRepl';
-import { DocumentIconContainer } from './DocumentIcon';
export type KVPScript = {
script: CompiledScript;
@@ -147,7 +144,15 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
const self = this;
const keys = Object.keys(ids).slice();
//for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
- for (const key of keys.sort()) {
+ for (const key of keys.sort((a: string, b: string) => {
+ const a_ = a.split('_')[0];
+ const b_ = b.split('_')[0];
+ if (a_ < b_) return -1;
+ if (a_ > b_) return 1;
+ if (a === a_) return -1;
+ if (b === b_) return 1;
+ return a === b ? 0 : a < b ? -1 : 1;
+ })) {
rows.push(
<KeyValuePair
doc={realDoc}
@@ -213,18 +218,18 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
document.addEventListener('pointerup', this.onDividerUp);
};
- getFieldView = async () => {
+ getFieldView = () => {
const rows = this.rows.filter(row => row.isChecked);
if (rows.length > 1) {
- const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document.data).title}`, _chromeHidden: true });
+ const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document).title}`, _chromeHidden: true });
for (const row of rows) {
- const field = this.createFieldView(DocCast(this.props.Document.data), row);
+ const field = this.createFieldView(DocCast(this.props.Document), row);
field && Doc.AddDocToList(parent, 'data', field);
row.uncheck();
}
return parent;
}
- return rows.length ? this.createFieldView(DocCast(this.props.Document.data), rows.lastElement()) : undefined;
+ return rows.length ? this.createFieldView(DocCast(this.props.Document), rows.lastElement()) : undefined;
};
createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 64f25cb22..b0d041bdd 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -14,6 +14,8 @@ import './KeyValueBox.scss';
import './KeyValuePair.scss';
import React = require('react');
import { DocCast } from '../../../fields/Types';
+import { Tooltip } from '@material-ui/core';
+import { DocumentOptions, FInfo } from '../../documents/Documents';
// Represents one row in a key value plane
@@ -67,7 +69,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
isSelected: returnFalse,
setHeight: returnFalse,
select: emptyFunction,
- dropAction: 'embed',
bringToFront: emptyFunction,
renderDepth: 1,
isContentActive: returnFalse,
@@ -110,11 +111,13 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
X
</button>
<input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
- <div className="keyValuePair-keyField" style={{ marginLeft: 35 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}>
- {'('.repeat(parenCount)}
- {props.fieldKey}
- {')'.repeat(parenCount)}
- </div>
+ <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description}>
+ <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}>
+ {'('.repeat(parenCount)}
+ {props.fieldKey}
+ {')'.repeat(parenCount)}
+ </div>
+ </Tooltip>
</div>
</td>
<td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 52f3575cb..4439be0cd 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -74,7 +74,9 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
if (docDragData && missingParams?.includes((e.target as any).textContent)) {
this.paramsDoc[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) => (d.onDragStart ? docDragData.draggedDocuments[i] : d)));
e.stopPropagation();
+ return true;
}
+ return false;
};
@observable _mouseOver = false;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index e86b881a8..9bcd04cf5 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -47,7 +47,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (separation > 100) {
const dragData = new DragManager.DocumentDragData([this.rootDoc]);
dragData.dropAction = 'embed';
- dragData.removeDropProperties = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick'];
+ dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick'];
DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
return true;
} else {
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index b5b8e660f..767f0291b 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,3 +1,7 @@
-.linkBox-container-interactive {
+.linkBox-container-interactive {
pointer-events: all;
-} \ No newline at end of file
+ width: 100%;
+}
+.linkBox-container {
+ width: 100%;
+}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 710d41471..efb949a47 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -18,7 +18,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.props.setContentView?.(this);
}
render() {
- if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true));
+ if (this.dataDoc.treeView_Open === undefined) setTimeout(() => (this.dataDoc.treeView_Open = true));
return (
<div className={`linkBox-container${this.props.isContentActive() ? '-interactive' : ''}`} style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }}>
<ComparisonBox
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 86191de63..d69009415 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -4,7 +4,7 @@ import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import wiki from 'wikijs';
import { Doc, DocCastAsync, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import { DirectLinks, Height, Width } from '../../../fields/DocSymbols';
import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
import { DocServer } from '../../DocServer';
diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss
index 4c3b8dabe..d4a7e18f2 100644
--- a/src/client/views/nodes/LoadingBox.scss
+++ b/src/client/views/nodes/LoadingBox.scss
@@ -12,6 +12,10 @@
text-overflow: ellipsis;
max-width: 80%;
text-align: center;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: center;
}
}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 93e54ffb7..cf017d746 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -61,7 +61,6 @@ const script = document.createElement('script');
script.defer = true;
script.async = true;
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`;
-console.log(script.src);
document.head.appendChild(script);
/**
@@ -103,7 +102,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
@computed get inlineTextAnnotations() {
- return this.allMapMarkers.filter(a => a.textInlineAnnotations);
+ return this.allMapMarkers.filter(a => a.text_inlineAnnotations);
}
@observable private _map: google.maps.Map = null as unknown as google.maps.Map;
@@ -512,9 +511,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this.addDocument(doc, annotationKey);
};
- pointerEvents = () => {
- return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
- };
+ pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+
@computed get annotationLayer() {
return (
<div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 72f37b62c..b3ae8242d 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -100,7 +100,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
@computed get inlineTextAnnotations() {
- return this.allMapMarkers.filter(a => a.textInlineAnnotations);
+ return this.allMapMarkers.filter(a => a.text_inlineAnnotations);
}
@observable private _map: google.maps.Map = null as unknown as google.maps.Map;
@@ -510,7 +510,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
pointerEvents = () => {
- return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
+ return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none';
};
@computed get annotationLayer() {
return (
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c210176b0..758b49655 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -213,7 +213,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
}
- brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._pdfViewer?.brushView(view);
+ brushView = (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => this._pdfViewer?.brushView(view, transTime);
sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
@@ -224,7 +224,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
focus = (anchor: Doc, options: DocFocusOptions) => {
this._initialScrollTarget = anchor;
- return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.presViewScroll)), options);
+ return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
};
getView = async (doc: Doc) => {
@@ -238,18 +238,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
ele = document.createElement('div');
ele.append(this._pdfViewer.selectionContent()!);
}
- const docAnchor = () => {
- const anchor = Docs.Create.PdfConfigDocument({
+ const docAnchor = () =>
+ Docs.Create.ConfigDocument({
title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
annotationOn: this.rootDoc,
});
- return anchor;
- };
const annoAnchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations(), true);
const anchor = annoAnchor ?? docAnchor();
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc);
anchor.text = ele?.textContent ?? '';
- anchor.textHtml = ele?.innerHTML;
+ anchor.text_html = ele?.innerHTML;
if (addAsAnnotation || annoAnchor) {
this.addDocument(anchor);
}
@@ -561,7 +559,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
</div>
);
}
- isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected() || (this.props.renderDepth === 0 && LightboxView.IsLightboxDocView(this.props.docViewPath()));
@computed get renderPdfView() {
TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
@@ -594,7 +591,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
pdf={this._pdf}
focus={this.focus}
url={this.pdfUrl!.url.pathname}
- isContentActive={this.isPdfContentActive}
anchorMenuClick={this.anchorMenuClick}
loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
setPdfViewer={this.setPdfViewer}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 04f11a5df..8fa2861b6 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -4,28 +4,28 @@ import * as React from 'react';
import { VideoField } from '../../../../fields/URLField';
import { Upload } from '../../../../server/SharedMediaTypes';
import { ViewBoxBaseComponent } from '../../DocComponent';
-import { FieldView } from '../FieldView';
+import { FieldView, FieldViewProps } from '../FieldView';
import { VideoBox } from '../VideoBox';
import { RecordingView } from './RecordingView';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Presentation } from '../../../util/TrackMovements';
import { Doc } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
-import { DocCast } from '../../../../fields/Types';
+import { BoolCast, DocCast } from '../../../../fields/Types';
+import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { Docs } from '../../../documents/Documents';
@observer
-export class RecordingBox extends ViewBoxBaseComponent() {
+export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(RecordingBox, fieldKey);
}
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
- constructor(props: any) {
- super(props);
- }
-
componentDidMount() {
+ this.props.setContentView?.(this);
Doc.SetNativeWidth(this.dataDoc, 1280);
Doc.SetNativeHeight(this.dataDoc, 720);
}
@@ -46,20 +46,63 @@ export class RecordingBox extends ViewBoxBaseComponent() {
this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client);
- this.dataDoc[this.fieldKey + '-recorded'] = true;
+ this.dataDoc[this.fieldKey + '_recorded'] = true;
// stringify the presentation and store it
if (presentation?.movements) {
const presCopy = { ...presentation };
presCopy.movements = presentation.movements.map(movement => ({ ...movement, doc: movement.doc[Id] })) as any;
- this.dataDoc[this.fieldKey + '-presentation'] = JSON.stringify(presCopy);
+ this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
}
};
+ Record: undefined | (() => void);
+ Pause: undefined | (() => void);
+ Finish: undefined | (() => void);
+ getControls = (record: () => void, pause: () => void, finish: () => void) => {
+ this.Record = record;
+ this.Pause = pause;
+ this.Finish = finish;
+ };
+
render() {
return (
<div className="recordingBox" ref={this._ref}>
- {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={DocCast(this.rootDoc.proto)?.[Id] || ''} />}
+ {!this.result && (
+ <RecordingView
+ forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])}
+ getControls={this.getControls}
+ setResult={this.setResult}
+ setDuration={this.setVideoDuration}
+ id={DocCast(this.rootDoc.proto)?.[Id] || ''}
+ />
+ )}
</div>
);
}
+ static screengrabber: RecordingBox | undefined;
}
+ScriptingGlobals.add(function toggleRecording(_readOnly_: boolean) {
+ if (_readOnly_) return RecordingBox.screengrabber ? true : false;
+ if (RecordingBox.screengrabber) {
+ RecordingBox.screengrabber.Pause?.();
+ setTimeout(() => {
+ RecordingBox.screengrabber?.Finish?.();
+ RecordingBox.screengrabber!.rootDoc.overlayX = 100;
+ RecordingBox.screengrabber!.rootDoc.overlayY = 100;
+ RecordingBox.screengrabber = undefined;
+ }, 100);
+ } else {
+ const screengrabber = Docs.Create.WebCamDocument('', {
+ _width: 384,
+ _height: 216,
+ });
+ screengrabber.overlayX = -400;
+ screengrabber.overlayY = 0;
+ screengrabber[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+ Doc.AddToMyOverlay(screengrabber);
+ DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
+ RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
+ RecordingBox.screengrabber.Record?.();
+ });
+ }
+}, 'toggle recording');
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 51eb774e2..0e386b093 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -21,6 +21,8 @@ interface IRecordingViewProps {
setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void;
setDuration: (seconds: number) => void;
id: string;
+ getControls: (record: () => void, pause: () => void, finish: () => void) => void;
+ forceTrackScreen: boolean;
}
const MAXTIME = 100000;
@@ -60,14 +62,14 @@ export function RecordingView(props: IRecordingViewProps) {
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));
+ let concatPres = (trackScreen || props.forceTrackScreen) && 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(file => ({file})))).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server));
+ const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles.map(file => ({ file })))).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);
@@ -132,7 +134,7 @@ export function RecordingView(props: IRecordingViewProps) {
videoRecorder.current.onstart = (event: any) => {
setRecording(true);
// start the recording api when the video recorder starts
- trackScreen && TrackMovements.Instance.start();
+ (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start();
};
videoRecorder.current.onstop = () => {
@@ -147,7 +149,7 @@ export function RecordingView(props: IRecordingViewProps) {
// 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]);
+ setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
}
// reset the temporary chunks
@@ -159,9 +161,7 @@ export function RecordingView(props: IRecordingViewProps) {
};
// if this is called, then we're done recording all the segments
- const finish = (e: React.PointerEvent) => {
- e.stopPropagation();
-
+ const finish = () => {
// call stop on the video recorder if active
videoRecorder.current?.state !== 'inactive' && videoRecorder.current?.stop();
@@ -176,8 +176,7 @@ export function RecordingView(props: IRecordingViewProps) {
setFinished(true);
};
- const pause = (e: React.PointerEvent) => {
- e.stopPropagation();
+ const pause = () => {
// if recording, then this is just a new segment
videoRecorder.current?.state === 'recording' && videoRecorder.current.stop();
};
@@ -217,6 +216,10 @@ export function RecordingView(props: IRecordingViewProps) {
return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds);
};
+ useEffect(() => {
+ props.getControls(record, pause, finish);
+ }, []);
+
return (
<div className="recording-container">
<div className="video-wrapper">
@@ -227,7 +230,19 @@ export function RecordingView(props: IRecordingViewProps) {
</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>
+ <div className="record-button-wrapper">
+ {recording ? (
+ <button
+ className="stop-button"
+ onPointerDown={e => {
+ e.stopPropagation();
+ pause();
+ }}
+ />
+ ) : (
+ <button className="record-button" onPointerDown={start} />
+ )}
+ </div>
{!recording &&
(videos.length > 0 ? (
@@ -236,7 +251,12 @@ export function RecordingView(props: IRecordingViewProps) {
<MdBackspace onPointerDown={undoPrevious} />
</IconContext.Provider>
<IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}>
- <FaCheckCircle onPointerDown={finish} />
+ <FaCheckCircle
+ onPointerDown={e => {
+ e.stopPropagation();
+ finish();
+ }}
+ />
</IconContext.Provider>
</div>
) : (
@@ -244,7 +264,7 @@ export function RecordingView(props: IRecordingViewProps) {
<label className="track-screen">
<input
type="checkbox"
- checked={trackScreen}
+ checked={trackScreen || props.forceTrackScreen}
onChange={e => {
setTrackScreen(e.target.checked);
}}
diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss
index 6fb5ea7b3..1e9b64a0b 100644
--- a/src/client/views/nodes/ScreenshotBox.scss
+++ b/src/client/views/nodes/ScreenshotBox.scss
@@ -1,6 +1,5 @@
.screenshotBox {
transform-origin: top left;
- background: white;
color: black;
// .screenshotBox-viewer {
// 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
@@ -12,46 +11,39 @@
#CANCAN {
canvas {
- width:100% !important;
+ width: 100% !important;
height: 100% !important;
}
}
-.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-cont-fullScreen {
+.screenshotBox-content,
+.screenshotBox-content-interactive,
+.screenshotBox-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;
}
-.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-content-fullScreen {
- height: Auto;
+.screenshotBox-content,
+.screenshotBox-content-interactive,
+.screenshotBox-content-fullScreen {
+ height: Auto;
}
.screenshotBox-uiButtons {
- background:dimgray;
- border: orange solid 1px;
position: absolute;
right: 25;
top: 0;
- width:25;
+ width: 22;
height: 25;
- .screenshotBox-snapshot{
- color : white;
- top :0px;
- right : 5px;
- position: absolute;
- background-color:rgba(50, 50, 50, 0.2);
- transform-origin: left top;
- pointer-events:all;
- }
- .screenshotBox-recorder{
- color : white;
- top :0px;
+ .screenshotBox-recorder {
+ color: white;
+ top: 4px;
left: 5px;
position: absolute;
- background-color:rgba(50, 50, 50, 0.2);
+ background-color: rgba(50, 50, 50, 0.2);
transform-origin: left top;
- pointer-events:all;
+ pointer-events: all;
}
}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 312b3c619..83a29f071 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -17,6 +17,7 @@ import { DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { Networking } from '../../Network';
import { CaptureManager } from '../../util/CaptureManager';
+import { SettingsManager } from '../../util/SettingsManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
@@ -116,7 +117,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
@observable private _videoRef: HTMLVideoElement | null = null;
@observable _screenCapture = false;
@computed get recordingStart() {
- return Cast(this.dataDoc[this.props.fieldKey + '-recordingStart'], DateField)?.date.getTime();
+ return Cast(this.dataDoc[this.props.fieldKey + '_recordingStart'], DateField)?.date.getTime();
}
constructor(props: any) {
@@ -224,20 +225,19 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
const aud_chunks: any = [];
this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data);
this._audioRec.onstop = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({file})));
+ const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({ file })));
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});
+ 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
@@ -270,14 +270,15 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
};
setupDictation = () => {
- if (this.dataDoc[this.fieldKey + '-dictation']) return;
+ 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));
+ const textField = Doc.LayoutFieldKey(dictationText);
dictationText._layout_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[`${textField}_recordingSource`] = this.dataDoc;
+ dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+ dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
+ this.dataDoc[this.fieldKey + '_dictation'] = dictationText;
};
videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth();
formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
@@ -313,11 +314,11 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
</>
</CollectionFreeFormView>
</div>
- <div style={{ background: 'white', position: 'relative', height: this.formattedPanelHeight() }}>
- {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : (
+ <div style={{ background: SettingsManager.Instance.userColor, position: 'relative', height: this.formattedPanelHeight() }}>
+ {!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : (
<FormattedTextBox
{...this.props}
- Document={DocCast(this.dataDoc[this.fieldKey + '-dictation'])}
+ Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])}
fieldKey={'text'}
PanelHeight={this.formattedPanelHeight}
select={emptyFunction}
@@ -335,8 +336,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
</div>
</div>
{!this.props.isSelected() ? null : (
- <div className="screenshotBox-uiButtons">
- <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording}>
+ <div className="screenshotBox-uiButtons" style={{ background: SettingsManager.Instance.userColor }}>
+ <div className="screenshotBox-recorder" style={{ color: SettingsManager.Instance.userBackgroundColor, background: SettingsManager.Instance.userVariantColor }} key="snap" onPointerDown={this.toggleRecording}>
<FontAwesomeIcon icon="file" size="lg" />
</div>
</div>
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 37fda14fc..7c8a1849e 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -270,8 +270,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
// sets field of the corresponding field key (param name) to be dropped document
@action
onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
- Doc.SetInPlace(this.rootDoc, fieldKey, de.complete.docDragData?.droppedDocuments[0], true);
- e.stopPropagation();
+ if (de.complete.docDragData) {
+ de.complete.docDragData.droppedDocuments.forEach(doc => Doc.SetInPlace(this.rootDoc, fieldKey, doc, true));
+ e.stopPropagation();
+ return true;
+ }
+ return false;
};
// deletes a param from all areas in which it is stored
@@ -606,7 +610,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
' ': {
dataProvider: (token: any) => this.handleToken(token),
component: (blob: any) => {
- console.log('Blob', blob);
return this.renderFuncListElement(blob.entity);
},
output: (item: any, trigger: any) => {
@@ -617,7 +620,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
'.': {
dataProvider: (token: any) => this.handleToken(token),
component: (blob: any) => {
- console.log('Blob', blob);
return this.renderFuncListElement(blob.entity);
},
output: (item: any, trigger: any) => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1f52c2d92..2177adeff 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -103,14 +103,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// returns the path of the audio file
@computed get audiopath() {
- const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null);
+ 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 ?? '';
}
// returns the presentation data if it exists, null otherwise
@computed get presentation() {
- const data = this.dataDoc[this.fieldKey + '-presentation'];
+ const data = this.dataDoc[this.fieldKey + '_presentation'];
return data ? JSON.parse(StrCast(data)) : null;
}
@@ -524,7 +524,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
icon: 'expand-arrows-alt',
});
// if the videobox was turned from a recording box
- if (this.dataDoc[this.fieldKey + '-recorded'] === true) {
+ if (this.dataDoc[this.fieldKey + '_recorded'] === true) {
subitems.push({
description: 'Recreate recording',
event: () => {
@@ -533,7 +533,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.dataDoc[this.props.fieldKey] = '';
this.dataDoc[this.fieldKey + '_duration'] = '';
// delete assoicated presentation data
- this.dataDoc[this.fieldKey + '-presentation'] = '';
+ this.dataDoc[this.fieldKey + '_presentation'] = '';
},
icon: 'expand-arrows-alt',
});
@@ -959,7 +959,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{...this.props}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
- dictationKey={this.fieldKey + '-dictation'}
+ dictationKey={this.fieldKey + '_dictation'}
mediaPath={this.audiopath}
thumbnails={() => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])}
renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2ff0245d2..febf8341e 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -51,7 +51,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
public static sidebarResizerWidth = 5;
static webStyleSheet = addStyleSheet();
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
- private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void);
+ private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void);
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -91,7 +91,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return DocListCast(this.dataDoc[this.annotationKey]);
}
@computed get inlineTextAnnotations() {
- return this.allAnnotations.filter(a => a.textInlineAnnotations);
+ return this.allAnnotations.filter(a => a.text_inlineAnnotations);
}
@computed get webField() {
return Cast(this.rootDoc[this.props.fieldKey], WebField)?.url;
@@ -125,11 +125,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._searchRef.current?.setRangeText(searchString);
});
}
- if (clear) {
- this._iframe?.contentWindow?.getSelection()?.empty();
- }
- if (searchString) {
- (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true);
+ try {
+ if (clear) {
+ this._iframe?.contentWindow?.getSelection()?.empty();
+ }
+ if (searchString) {
+ (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true);
+ }
+ } catch (e) {
+ console.log('WebBox search error', e);
}
return true;
};
@@ -277,8 +281,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this._savedAnnotations;
};
- setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
- brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
+ setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => (this._setBrushViewer = func);
+ brushView = (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => this._setBrushViewer?.(view, transTime);
focus = (anchor: Doc, options: DocFocusOptions) => {
if (anchor !== this.rootDoc && this._outerRef.current) {
const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
@@ -299,7 +303,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
getView = (doc: Doc) => {
if (Doc.AreProtosEqual(doc, this.rootDoc)) return new Promise<Opt<DocumentView>>(res => res(this.props.DocumentView?.()));
if (this.rootDoc.layout_fieldKey === 'layout_icon') this.props.DocumentView?.().iconify();
- const webUrl = WebCast(doc.presData)?.url;
+ const webUrl = WebCast(doc.config_data)?.url;
if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
@@ -323,14 +327,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
} catch (e) {}
const anchor =
this._getAnchor(this._savedAnnotations, false) ??
- Docs.Create.WebConfigDocument({
+ Docs.Create.ConfigDocument({
title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop),
y: NumCast(this.layoutDoc._layout_scrollTop),
annotationOn: this.rootDoc,
});
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.rootDoc);
anchor.text = ele?.textContent ?? '';
- anchor.textHtml = ele?.innerHTML;
+ anchor.text_html = ele?.innerHTML;
//addAsAnnotation &&
this.addDocumentWrapper(anchor);
return anchor;
@@ -757,7 +761,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
- (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.presData = new WebField(this._url)));
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.config_data = new WebField(this._url)));
return this.addDocument(doc, annotationKey);
};
@@ -918,7 +922,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
ScreenToLocalTransform={this.scrollXf}
NativeDimScaling={returnOne}
focus={this.focus}
- dropAction="embed"
childFilters={childFilters}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
@@ -1003,8 +1006,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
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');
- annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None ? 'all' : 'none');
+ pointerEvents = () =>
+ !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
+ ? 'all' //
+ : 'none';
+ annotationPointerEvents = () => (this.props.isContentActive() && (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
render() {
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
@@ -1013,7 +1019,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div
className="webBox"
ref={this._mainCont}
- style={{ pointerEvents: this.pointerEvents(), position: SnappingManager.GetIsDragging() ? 'absolute' : undefined, display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined }}>
+ style={{
+ pointerEvents: this.pointerEvents(), //
+ position: SnappingManager.GetIsDragging() ? 'absolute' : undefined,
+ display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
+ }}>
<div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} />
<div
className="webBox-container"
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
deleted file mode 100644
index 5bba51ec8..000000000
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ /dev/null
@@ -1,953 +0,0 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-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 * as React from 'react';
-import { ColorState, SketchPicker } from 'react-color';
-import { Doc, StrListCast } from '../../../../fields/Doc';
-import { Height, Width } from '../../../../fields/DocSymbols';
-import { InkTool } from '../../../../fields/InkField';
-import { ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { WebField } from '../../../../fields/URLField';
-import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, Utils } from '../../../../Utils';
-import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
-import { LinkManager } from '../../../util/LinkManager';
-import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
-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, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke';
-import { InkTranscription } from '../../InkTranscription';
-import { StyleProp } from '../../StyleProvider';
-import { FieldView, FieldViewProps } from '.././FieldView';
-import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView';
-import { OpenWhere } from '../DocumentView';
-import { RichTextMenu } from '../formattedText/RichTextMenu';
-import { WebBox } from '../WebBox';
-import { FontIconBadge } from './FontIconBadge';
-import './FontIconBox.scss';
-
-export enum ButtonType {
- TextButton = 'textBtn',
- MenuButton = 'menuBtn',
- DropdownList = 'drpdownList',
- DropdownButton = 'drpdownBtn',
- ClickButton = 'clickBtn',
- DoubleButton = 'dblBtn',
- ToggleButton = 'tglBtn',
- ColorButton = 'colorBtn',
- ToolButton = 'toolBtn',
- NumberSliderButton = 'numSliderBtn',
- NumberDropdownButton = 'numDropdownBtn',
- NumberInlineButton = 'numInlineBtn',
- EditableText = 'editableText',
-}
-
-export interface ButtonProps extends FieldViewProps {
- type?: ButtonType;
-}
-@observer
-export class FontIconBox extends DocComponent<ButtonProps>() {
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(FontIconBox, fieldKey);
- }
- @observable noTooltip = false;
- showTemplate = (): void => {
- const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight);
- };
- 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.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' });
- }
- };
-
- static GetShowLabels() {
- return BoolCast(Doc.UserDoc()._showLabel);
- }
- static SetShowLabels(show: boolean) {
- Doc.UserDoc()._showLabel = show;
- }
- static GetRecognizeGestures() {
- return BoolCast(Doc.UserDoc()._recognizeGestures);
- }
- static SetRecognizeGestures(show: boolean) {
- Doc.UserDoc()._recognizeGestures = show;
- }
-
- // Determining UI Specs
- @computed get label() {
- return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title));
- }
- Icon = (color: string) => {
- const icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
- const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />;
- return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
- };
- @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:
- * - Main menu button (LHS)
- * - Tool button
- * - Expandable button (CollectionLinearView)
- * - Button inside of CollectionLinearView vs. outside of CollectionLinearView
- * - Action button
- * - Dropdown button
- * - Color button
- * - Dropdown list
- * - Number button
- **/
-
- _batch: UndoManager.Batch | undefined = undefined;
- /**
- * Number button
- */
- @computed get numberSliderButton() {
- const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined });
- // Script for checking the outcome of the toggle
- const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
- const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
-
- const dropdown = (
- <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()}>
- <input
- className="menu-slider"
- type="range"
- step="1"
- min={NumCast(this.rootDoc.numBtnMin, 0)}
- max={NumCast(this.rootDoc.numBtnMax, 100)}
- //readOnly={true}
- value={checkResult}
- onPointerDown={() => (this._batch = UndoManager.StartBatch('num slider changing'))}
- onPointerUp={() => this._batch?.end()}
- onChange={undoable(e => {
- e.stopPropagation();
- numScript(Number(e.target.value));
- }, 'set num value')}
- />
- </div>
- );
- return (
- <div
- className="menuButton numBtn slider"
- onPointerDown={e => e.stopPropagation()}
- onClick={action(() => {
- this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
- this.noTooltip = this.rootDoc.dropDownOpen;
- Doc.UnBrushAllDocs();
- })}>
- {checkResult}
- {label}
- {this.rootDoc.dropDownOpen ? dropdown : null}
- </div>
- );
- }
- /**
- * Number button
- */
- @computed get numberDropdownButton() {
- const numScript = (value?: number) => ScriptCast(this.rootDoc.script)?.script.run({ self: this.rootDoc, value, _readOnly_: value === undefined });
-
- const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
-
- const items: number[] = [];
- for (let i = 0; i < 100; i += 2) items.push(i);
-
- const list = items.map(value => {
- return (
- <div
- className="list-item"
- key={`${value}`}
- style={{
- backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined,
- }}
- onClick={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}>
- {value}
- </div>
- );
- });
- return (
- <div className="menuButton numBtn list">
- <div className="button" onClick={undoable(e => numScript(Number(checkResult) - 1), `${this.rootDoc.title} decrement value`)}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="minus" />
- </div>
- <div
- className={`button ${'number'}`}
- onPointerDown={e => {
- e.stopPropagation();
- e.preventDefault();
- }}
- onClick={action(() => {
- this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
- this.noTooltip = this.rootDoc.dropDownOpen;
- Doc.UnBrushAllDocs();
- })}>
- <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} readOnly={true} onChange={undoable(e => numScript(Number(e.target.value)), `${this.rootDoc.title} button set value`)} />
- </div>
- <div className={`button`} onClick={undoable(e => numScript(Number(checkResult) + 1), `${this.rootDoc.title} increment value`)}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} />
- </div>
-
- {this.rootDoc.dropDownOpen ? (
- <div>
- <div className="menuButton-dropdownList" style={{ left: '25%' }}>
- {list}
- </div>
- <div
- className="dropbox-background"
- onClick={action(e => {
- e.stopPropagation();
- this.rootDoc.dropDownOpen = false;
- this.noTooltip = false;
- Doc.UnBrushAllDocs();
- })}
- />
- </div>
- ) : null}
- </div>
- );
- }
- /**
- * Number button
- */
- @computed get numberInlineButton() {
- return <div />;
- }
-
- /**
- * Dropdown button
- */
- @computed get dropdownButton() {
- const active: string = StrCast(this.rootDoc.dropDownOpen);
- 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}`}
- style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
- onClick={action(() => {
- this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
- this.noTooltip = this.rootDoc.dropDownOpen;
- Doc.UnBrushAllDocs();
- })}>
- {this.Icon(color)}
- {!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}
- </div>
- );
- }
-
- /**
- * Dropdown list
- */
- @computed get dropdownListButton() {
- const active: string = StrCast(this.rootDoc.dropDownOpen);
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-
- const script = ScriptCast(this.rootDoc.script);
-
- let noviceList: string[] = [];
- let text: string | undefined;
- let dropdown = true;
- let icon: IconProp = 'caret-down';
- try {
- if (script?.script.originalScript.startsWith('setView')) {
- const selected = SelectionManager.Docs().lastElement();
- if (selected) {
- if (StrCast(selected.type) === DocumentType.COL) {
- text = StrCast(selected._type_collection);
- } else {
- dropdown = false;
- text = selected.type === DocumentType.RTF ? 'Text' : StrCast(selected.type);
- icon = Doc.toIcon(selected);
- }
- } else {
- dropdown = false;
- icon = 'globe-asia';
- text = 'User Default';
- }
- noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
- } else text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
- } catch (e) {
- console.log(e);
- }
-
- // Get items to place into the list
- const list = this.buttonList
- .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
- .map(value => (
- <div
- className="list-item"
- key={value}
- style={{
- fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined,
- backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
- }}
- onClick={undoable(() => script.script.run({ self: this.rootDoc, value }), value)}>
- {value[0].toUpperCase() + value.slice(1)}
- </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
- ? action(() => {
- this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
- this.noTooltip = this.rootDoc.dropDownOpen;
- Doc.UnBrushAllDocs();
- })
- : 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 ? (
- <div>
- <div className="menuButton-dropdownList" style={{ left: 0 }}>
- {list}
- </div>
- <div
- className="dropbox-background"
- onClick={action(e => {
- e.stopPropagation();
- this.rootDoc.dropDownOpen = false;
- this.noTooltip = false;
- Doc.UnBrushAllDocs();
- })}
- />
- </div>
- ) : null}
- </div>
- );
- }
-
- @observable colorPickerClosed: boolean = true;
- @computed get colorScript() {
- return ScriptCast(this.rootDoc.script);
- }
-
- colorPicker = (curColor: string) => {
- const change = (value: ColorState, ev: MouseEvent) => {
- ev.preventDefault();
- ev.stopPropagation();
- const s = this.colorScript;
- s && undoBatch(() => s.script.run({ self: this.rootDoc, 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 as any /* SketchPicker passes the mouse event to the callback, but the type system doesn't know that */} 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({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';
-
- const label =
- !this.label || !FontIconBox.GetShowLabels() ? null : (
- <div className="fontIconBox-label" style={{ color, backgroundColor }}>
- {this.label}
- </div>
- );
-
- return (
- <div
- className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')} ${this.colorPickerClosed}`}
- style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
- onClick={action(e => {
- this.colorPickerClosed = !this.colorPickerClosed;
- this.noTooltip = !this.colorPickerClosed;
- setTimeout(() => Doc.UnBrushAllDocs());
- e.stopPropagation();
- })}
- onPointerDown={e => e.stopPropagation()}>
- {this.Icon(color)}
- <div className="colorButton-color" style={{ backgroundColor: curColor }} />
- {label}
- {/* {dropdownCaret} */}
- {this.colorPickerClosed ? null : (
- <div>
- <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={e => e.stopPropagation()}>
- {this.colorPicker(curColor)}
- </div>
- <div
- className="dropbox-background"
- onPointerDown={action(e => {
- e.preventDefault();
- e.stopPropagation();
- this.colorPickerClosed = true;
- this.noTooltip = false;
- Doc.UnBrushAllDocs();
- })}
- />
- </div>
- )}
- </div>
- );
- }
-
- @computed get toggleButton() {
- // Determine the type of toggle button
- const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle);
- const buttonText: string = StrCast(this.rootDoc.buttonText);
- // Colors
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-
- // Button label
- 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" />
- </label>
- </div>
- );
- } else {
- return (
- <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
- {this.Icon(color)}
- {label}
- </div>
- );
- }
- }
-
- /**
- * Default
- */
- @computed get defaultButton() {
- 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}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
- <div className="menuButton-wrap">
- {this.Icon(color)}
- {!this.label || !FontIconBox.GetShowLabels() ? null : (
- <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
- {' '}
- {this.label}{' '}
- </div>
- )}
- </div>
- </div>
- );
- }
-
- @computed get editableText() {
- // 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 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} oneLine={true} contents={checkResult} />
- </div>
- </div>
- );
- }
-
- 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 = (noBackground: boolean = false) =>
- !this.label || !FontIconBox.GetShowLabels() ? null : (
- <div className="fontIconBox-label" style={{ color, backgroundColor: noBackground ? 'transparent' : backgroundColor }}>
- {this.label}
- </div>
- );
- // TODO:glr Add label of button type
- let button: JSX.Element = this.defaultButton;
-
- // prettier-ignore
- switch (this.type) {
- case ButtonType.EditableText: return this.editableText;
- case ButtonType.DropdownList: button = this.dropdownListButton; break;
- case ButtonType.ColorButton: button = this.colorButton; break;
- case ButtonType.NumberDropdownButton: button = this.numberDropdownButton; break;
- case ButtonType.NumberInlineButton: button = this.numberInlineButton; break;
- case ButtonType.NumberSliderButton: button = this.numberSliderButton; break;
- case ButtonType.DropdownButton: button = this.dropdownButton; break;
- case ButtonType.ToggleButton: button = this.toggleButton; break;
- case ButtonType.TextButton:
- // Script for checking the outcome of the toggle
- const script = ScriptCast(this.rootDoc.script);
- const checkResult = script?.script.run({ _readOnly_: true }).result;
- button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor:checkResult ?? backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
- {this.Icon(color)}
- {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
- {label()}
- </div>
- );
- break;
- case ButtonType.ClickButton:
- case ButtonType.ToolButton: button = (
- <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}>
- {this.Icon(color)}
- {label()}
- </div>
- );
- break;
- case ButtonType.MenuButton: button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
- {this.Icon(color)}
- {label(true)}
- <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
- </div>
- );
- break;
- }
-
- return !this.layoutDoc.toolTip || this.noTooltip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>;
- }
-}
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setView(view: string) {
- const selected = SelectionManager.Docs().lastElement();
- selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
-});
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
- const selectedViews = SelectionManager.Views();
- if (Doc.ActiveTool !== InkTool.None) {
- if (checkResult) {
- return ActiveFillColor();
- }
- SetActiveFillColor(color ?? 'transparent');
- } else if (selectedViews.length) {
- if (checkResult) {
- const selView = selectedViews.lastElement();
- const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
- const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
- const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
- return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent';
- }
- selectedViews.forEach(dv => {
- const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
- const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
- const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
- if (contentFrameNumber !== undefined) {
- CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color });
- } else {
- dv.rootDoc['_' + fieldKey] = color;
- }
- });
- } else {
- const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : [];
- if (checkResult) {
- return selected.lastElement()?._backgroundColor ?? 'transparent';
- }
- selected.forEach(doc => (doc._backgroundColor = color));
- }
-});
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
- if (checkResult) {
- return Doc.SharingDoc().userColor;
- }
- Doc.SharingDoc().userColor = undefined;
- Doc.GetProto(Doc.SharingDoc()).userColor = color;
- Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
-});
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
- const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
- 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');
-});
-
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- // prettier-ignore
- const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
- ['grid', {
- checkResult: (doc:Doc) => doc._freeform_backgroundGrid,
- setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
- }],
- ['snaplines', {
- checkResult: (doc:Doc) => doc._freeform_snapLines,
- setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines,
- }],
- ['viewAll', {
- checkResult: (doc:Doc) => doc._freeform_fitContentsToBox,
- setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
- }],
- ['clusters', {
- waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
- checkResult: (doc:Doc) => doc._freeform_useClusters,
- setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters,
- }],
- ['arrange', {
- waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
- checkResult: (doc:Doc) => doc._autoArrange,
- setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange,
- }],
- ['flashcards', {
- checkResult: (doc:Doc) => Doc.UserDoc().defaultToFlashcards,
- setDoc: (doc:Doc) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
- }],
- ]);
-
- if (checkResult) {
- return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent';
- }
- const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
- SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv));
- setTimeout(() => batch.end(), 100);
-});
-ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: any, checkResult?: boolean) {
- const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- const selected = SelectionManager.Docs().lastElement();
- // prettier-ignore
- const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([
- ['font', {
- checkResult: () => RichTextMenu.Instance?.fontFamily,
- setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
- }],
- ['highlight', {
- checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
- setDoc: () => value && RichTextMenu.Instance.setHighlight(value),
- }],
- ['fontColor', {
- checkResult: () => RichTextMenu.Instance?.fontColor,
- setDoc: () => value && RichTextMenu.Instance.setColor(value),
- }],
- ['fontSize', {
- checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
- setDoc: () => {
- if (typeof value === 'number') value = value.toString();
- if (value && Number(value).toString() === value) value += 'px';
- RichTextMenu.Instance.setFontSize(value);
- },
- }],
- ]);
-
- if (checkResult) {
- return map.get(attr)?.checkResult();
- }
- map.get(attr)?.setDoc?.();
-});
-
-type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal';
-type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
-ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
- const textView = RichTextMenu.Instance?.TextView;
- const editorView = textView?.EditorView;
- // prettier-ignore
- const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) =>
- [ where, { checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false),
- toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]);
- // prettier-ignore
- const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
- [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
- toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
- // prettier-ignore
- const attrs:attrfuncs[] = [
- ['dictation', { checkResult: () => textView?._recording ? true:false,
- toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }],
- ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
- toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
- ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
- ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
- ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
-
- const map = new Map(attrs.concat(alignments).concat(listings));
- if (checkResult) return map.get(charStyle)?.checkResult() ? Colors.MEDIUM_BLUE : 'transparent';
- map.get(charStyle)?.toggle();
-});
-
-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[Width]();
- const height = d[Height]();
- 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[Height]();
- newCollection.width = newCollection[Width]();
- }
-
- // 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();
-}
-
-function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
- InkTranscription.Instance?.createInkGroup();
- if (checkResult) {
- return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool
- ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures)
- ? Colors.MEDIUM_BLUE
- : Colors.MEDIUM_BLUE_ALT
- : 'transparent';
- }
- runInAction(() => {
- if (GestureOverlay.Instance) {
- GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
- }
- if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
- if (GestureOverlay.Instance.InkShape === tool && !keepPrim) {
- Doc.ActiveTool = InkTool.None;
- GestureOverlay.Instance.InkShape = undefined;
- } else {
- Doc.ActiveTool = InkTool.Pen;
- GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
- }
- } else if (tool) {
- // pen or eraser
- if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) {
- Doc.ActiveTool = InkTool.None;
- } else {
- Doc.ActiveTool = tool as any;
- GestureOverlay.Instance.InkShape = undefined;
- }
- } else {
- Doc.ActiveTool = InkTool.None;
- }
- });
-}
-
-ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- // prettier-ignore
- const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
- ['inkMask', {
- checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask()) ? Colors.MEDIUM_BLUE : 'transparent'),
- setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask),
- setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
- }],
- ['fillColor', {
- checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"),
- setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
- setMode: () => SetActiveFillColor(StrCast(value)),
- }],
- [ 'strokeWidth', {
- checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()),
- setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)),
- setMode: () => SetActiveInkWidth(value.toString()),
- }],
- ['strokeColor', {
- checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
- setInk: (doc: Doc) => (doc.color = String(value)),
- setMode: () => SetActiveInkColor(StrCast(value)),
- }],
- ]);
-
- if (checkResult) {
- return map.get(option)?.checkResult();
- }
- map.get(option)?.setMode();
- SelectionManager.Docs()
- .filter(doc => doc.type === DocumentType.INK)
- .map(doc => map.get(option)?.setInk(doc));
-});
-
-/** WEB
- * webSetURL
- **/
-ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement();
- if (selected?.rootDoc.type === DocumentType.WEB) {
- if (checkResult) {
- return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
- }
- selected.ComponentView?.setData?.(url);
- //selected.rootDoc.data = new WebField(url);
- }
-});
-ScriptingGlobals.add(function webForward(checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
- if (checkResult) {
- return selected?.forward(checkResult) ? undefined : 'lightGray';
- }
- selected?.forward();
-});
-ScriptingGlobals.add(function webBack(checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
- if (checkResult) {
- return selected?.back(checkResult) ? undefined : 'lightGray';
- }
- selected?.back();
-});
-
-/** Schema
- * toggleSchemaPreview
- **/
-ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- if (checkResult && selected) {
- const result: boolean = NumCast(selected.schema_previewWidth) > 0;
- if (result) return Colors.MEDIUM_BLUE;
- else return 'transparent';
- } else if (selected) {
- if (NumCast(selected.schema_previewWidth) > 0) {
- selected.schema_previewWidth = 0;
- } else {
- selected.schema_previewWidth = 200;
- }
- }
-});
-ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- if (checkResult && selected) {
- return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent';
- }
- if (selected) {
- selected._schema_singleLine = !selected._schema_singleLine;
- }
-});
-
-/** STACK
- * groupBy
- */
-ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
- SelectionManager.Docs().map(doc => (doc._text_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;
-});
diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
deleted file mode 100644
index 74c3c563c..000000000
--- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { Component } from 'react';
-import { BoolCast, StrCast } from '../../../../../fields/Types';
-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() {
- const active: string = StrCast(this.props.rootDoc.dropDownOpen);
-
- const script: string = StrCast(this.props.rootDoc.script);
- const scriptCheck: string = script + '(undefined, true)';
- const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
-
- const stroke: boolean = false;
- // if (script === "setStrokeColor") {
- // stroke = true;
- // const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result;
- // const width = 20 + (checkWidth / 100) * 70;
- // const height = 20 + (checkWidth / 100) * 70;
- // strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />);
- // }
-
- const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb'];
-
- const colorBox = (func: (color: ColorState) => void) => <SketchPicker disableAlpha={!stroke} onChange={func} color={boolResult ? boolResult : '#FFFFFF'} presetColors={colorOptions} />;
- 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>
- );
-
- const dropdownCaret = (
- <div className="menuButton-dropDown" style={{ borderBottomRightRadius: active ? 0 : undefined }}>
- <FontAwesomeIcon icon={'caret-down'} color={this.props.color} size="sm" />
- </div>
- );
-
- const click = (value: ColorState) => {
- const hex: string = value.hex;
- const s = ScriptField.MakeScript(script + '("' + hex + '", false)');
- if (s) {
- s.script.run().result;
- }
- };
- return (
- <div
- className={`menuButton ${this.props.type} ${active}`}
- style={{ color: this.props.color, borderBottomLeftRadius: active ? 0 : undefined }}
- onClick={() => (this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen)}
- onPointerDown={e => e.stopPropagation()}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
- <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : '#FFFFFF' }} />
- {label}
- {/* {dropdownCaret} */}
- {this.props.rootDoc.dropDownOpen ? (
- <div>
- <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()}>
- {colorBox(click)}
- </div>
- <div
- className="dropbox-background"
- onClick={e => {
- e.stopPropagation();
- this.props.rootDoc.dropDownOpen = false;
- }}
- />
- </div>
- ) : null}
- </div>
- );
- }
-}
diff --git a/src/client/views/nodes/button/colorDropdown/index.ts b/src/client/views/nodes/button/colorDropdown/index.ts
deleted file mode 100644
index 1147d6457..000000000
--- a/src/client/views/nodes/button/colorDropdown/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './ColorDropdown'; \ No newline at end of file
diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx
deleted file mode 100644
index 5d7d55863..000000000
--- a/src/client/views/nodes/button/textButton/TextButton.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { Component } from 'react';
-import { BoolCast } from '../../../../../fields/Types';
-import { IButtonProps } from '../ButtonInterface';
-
-export class TextButton extends Component<IButtonProps> {
- render() {
- const type = this.props.type;
- // 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>
- );
- }
-}
diff --git a/src/client/views/nodes/button/textButton/index.ts b/src/client/views/nodes/button/textButton/index.ts
deleted file mode 100644
index 01d62eb7e..000000000
--- a/src/client/views/nodes/button/textButton/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './TextButton'; \ No newline at end of file
diff --git a/src/client/views/nodes/button/toggleButton/ToggleButton.tsx b/src/client/views/nodes/button/toggleButton/ToggleButton.tsx
deleted file mode 100644
index dca6487d8..000000000
--- a/src/client/views/nodes/button/toggleButton/ToggleButton.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { Component } from 'react';
-import { BoolCast } from '../../../../../fields/Types';
-import { Colors } from '../../../global/globalEnums';
-import { IButtonProps } from '../ButtonInterface';
-
-export class ToggleButton extends Component<IButtonProps> {
- render() {
- const type = this.props.type;
- // Determine the type of toggle button
- const switchToggle: boolean = BoolCast(this.props.rootDoc.switchToggle);
-
- if (switchToggle) {
- return (
- <div className={`menuButton ${type} ${'switch'}`}>
- <label className="switch">
- <input type="checkbox"
- checked={this.props.backgroundColor === Colors.MEDIUM_BLUE}
- />
- <span className="slider round"></span>
- </label>
- </div>
- );
- } else {
- return (
- <div className={`menuButton ${type}`}
- style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${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/button/toggleButton/index.ts b/src/client/views/nodes/button/toggleButton/index.ts
deleted file mode 100644
index cdb9c527c..000000000
--- a/src/client/views/nodes/button/toggleButton/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './ToggleButton'; \ 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 b4fb7a44e..d5ad128fe 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -17,6 +17,7 @@ import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
import React = require('react');
+import { Transform } from '../../../util/Transform';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -113,6 +114,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
componentWillUnmount() {
this._reactionDisposer?.();
}
+ return100 = () => 100;
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
@@ -123,7 +125,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
col={0}
deselectCell={emptyFunction}
selectCell={emptyFunction}
- maxWidth={this.props.hideKey ? undefined : () => 100}
+ maxWidth={this.props.hideKey ? undefined : this.return100}
columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero}
selectedCell={() => [this._dashDoc!, 0]}
fieldKey={this._fieldKey}
@@ -135,6 +137,8 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
allowCRs={true}
oneLine={!this._expanded}
finishEdit={action(() => (this._expanded = false))}
+ transform={Transform.Identity}
+ menuTarget={null}
/>
</div>
);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 109b62e6f..348bdd79e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -84,7 +84,6 @@ audiotag:hover {
height: 11;
}
-.formattedTextBox-outer-selected,
.formattedTextBox-outer {
position: relative;
overflow: auto;
@@ -92,9 +91,6 @@ audiotag:hover {
width: 100%;
height: unset;
}
-.formattedTextBox-outer-selected {
- cursor: text;
-}
.formattedTextBox-sidebar-handle {
position: absolute;
@@ -148,10 +144,8 @@ audiotag:hover {
}
.formattedTextBox-inner-rounded,
-.formattedTextBox-inner-rounded-selected,
.formattedTextBox-inner,
-.formattedTextBox-inner-minimal,
-.formattedTextBox-inner-selected {
+.formattedTextBox-inner-minimal {
height: 100%;
white-space: pre-wrap;
.ProseMirror:hover {
@@ -169,17 +163,6 @@ audiotag:hover {
border-width: 1px;
}
}
-.formattedTextBox-inner-rounded-selected,
-.formattedTextBox-inner-selected {
- > .ProseMirror {
- padding: 10px;
- }
-}
-.formattedTextBox-outer-selected {
- > .ProseMirror:hover {
- background: unset;
- }
-}
.gpt-typing-wrapper {
padding: 10px;
@@ -640,7 +623,6 @@ footnote::before {
}
}
- .formattedTextBox-outer-selected,
.formattedTextBox-outer {
position: relative;
overflow: auto;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 115777c18..2afbbb457 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,8 +12,8 @@ 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 { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols';
+import { Doc, DocListCast, StrListCast, Field, Opt } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, Height, Width, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -34,7 +34,6 @@ import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { MakeTemplate } from '../../../util/DropConverter';
-import { IsFollowLinkScript } from '../../../util/LinkFollower';
import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
@@ -97,6 +96,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
public _applyingChange: string = '';
+ private _finishingLink = false;
private _searchIndex = 0;
private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
@@ -156,7 +156,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return this.dataDoc?.mediaState === 'recording';
}
set _recording(value) {
- !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined);
+ !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? 'recording' : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this.props);
@@ -244,9 +244,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc;
- const anchor = Docs.Create.TextConfigDocument({ annotationOn: this.rootDoc });
+ const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc.title), annotationOn: this.rootDoc });
this.addDocument(anchor);
+ this._finishingLink = true;
this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
+ this._finishingLink = false;
PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.rootDoc);
return anchor;
};
@@ -297,7 +299,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
dispatchTransaction = (tx: Transaction) => {
- if (this._editorView) {
+ if (this._editorView && (this._editorView as any).docView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
@@ -305,13 +307,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n');
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
- const prevLayoutData = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
+ const templateData = this.rootDoc !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const effectiveAcl = GetEffectiveAcl(dataDoc);
- const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
+ const removeSelection = (json: string | undefined) => json?.replace(/"selection":.*/, '');
- if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
+ if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].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('#')) {
@@ -325,12 +327,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._applyingChange = this.fieldKey;
const textChange = newText !== prevData?.Text;
textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
- if ((!prevData && !protoData) || newText || (!newText && !protoData)) {
+ if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
// 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(newJson) !== removeSelection(prevLayoutData?.Data)) {
+ if ((this._finishingLink || this.props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
const numstring = NumCast(dataDoc[this.fieldKey], null);
dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
- dataDoc[this.fieldKey + '_noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText });
unchanged = false;
}
@@ -415,6 +417,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
+ // this.prepareForTyping();
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
};
@@ -457,8 +460,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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 ??
- (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)) ||
- DocUtils.MakeLink(this.props.Document, target, { link_relationship: LinkManager.AutoKeywords })!);
+ (LinkManager.Links(this.rootDoc).find(
+ link =>
+ Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && //
+ Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)
+ ) ||
+ DocUtils.MakeLink(this.rootDoc, target, { link_relationship: 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 ?? []));
@@ -531,36 +538,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
+ if (de.complete.annoDragData) {
+ de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
+ e.stopPropagation();
+ return true;
+ }
const dragData = de.complete.docDragData;
if (dragData) {
- const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0];
- // replace text contents whend dragging with Alt
- if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
- if (draggedDoc.data instanceof RichTextField) {
- Doc.GetProto(this.dataDoc)[this.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
- e.stopPropagation();
- }
- // embed document when dragg marked as embed
- } else if (de.embedKey) {
- const target = dragData.droppedDocuments[0];
- const node = schema.nodes.dashDoc.create({
- width: target[Width](),
- height: target[Height](),
- title: 'dashDoc',
- docId: target[Id],
- float: 'unset',
- });
- if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) {
- dragData.removeDocument?.(dragData.draggedDocuments[0]);
+ const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+ const effectiveAcl = GetEffectiveAcl(dataDoc);
+ let added = [AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl);
+ const draggedDoc = dragData.draggedDocuments.lastElement();
+ if (added) {
+ // replace text contents when dragging with Alt
+ if (de.altKey) {
+ const fieldKey = Doc.LayoutFieldKey(draggedDoc);
+ if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+ Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
+ }
+
+ // embed document when drag marked as embed
+ } else if (de.embedKey) {
+ const node = schema.nodes.dashDoc.create({
+ width: draggedDoc[Width](),
+ height: draggedDoc[Height](),
+ title: 'dashDoc',
+ docId: draggedDoc[Id],
+ float: 'unset',
+ });
+ if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) {
+ added = dragData.removeDocument?.(draggedDoc) ? true : false;
+ }
+ if (added) {
+ draggedDoc._freeform_fitContentsToBox = true;
+ Doc.SetContainer(draggedDoc, this.rootDoc);
+ const view = this._editorView!;
+ view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
+ }
}
- target._freeform_fitContentsToBox = true;
- target.embedContainer = this.rootDoc;
- 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
+ !added && e.preventDefault();
+ e.stopPropagation();
+ return added;
}
+ return false;
};
getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null {
@@ -709,7 +730,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@undoBatch
showTargetTrail = (anchor: Doc) => {
- const trail = DocCast(anchor.presTrail);
+ const trail = DocCast(anchor.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
this.props.addDocTab(trail, OpenWhere.replaceRight);
@@ -826,7 +847,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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' });
+ appearanceItems.push({ description: 'Change Style...', 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.noviceMode &&
@@ -865,11 +886,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const optionItems = options && 'subitems' in options ? options.subitems : [];
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
- optionItems.push({
- description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
- event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
- icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
- });
+ this.props.renderDepth &&
+ optionItems.push({
+ description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
+ event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
+ icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
+ });
!Doc.noviceMode &&
optionItems.push({
description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
@@ -909,7 +931,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
if (image_url) {
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] });
- const source = Utils.prepend(result.accessPaths.agnostic.client);
+ const source = result.accessPaths.agnostic.client;
const newDoc = Docs.Create.ImageDocument(source, {
x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
y: NumCast(this.rootDoc.y),
@@ -963,7 +985,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this._editorView && this._recordingStart) {
if (this._break) {
const textanchorFunc = () => {
- const tanch = Docs.Create.TextConfigDocument({ title: 'dictation anchor' });
+ const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' });
return this.addDocument(tanch) ? tanch : undefined;
};
const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
@@ -1003,7 +1025,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (sel.from !== sel.to) {
const anchor =
anchorDoc ??
- Docs.Create.TextConfigDocument({
+ Docs.Create.ConfigDocument({
//
title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')',
annotationOn: this.dataDoc,
@@ -1023,6 +1045,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
anchor.text = selectedText;
+ anchor.title = selectedText.substring(0, 30);
return anchor;
}
return anchorDoc ?? this.rootDoc;
@@ -1138,11 +1161,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
() => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
- if (layout_autoHeight && newHeight && newHeight !== this.rootDoc.height && !this.props.dontRegisterView) {
+ if (
+ (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this.props.isSelected()) && //
+ layout_autoHeight &&
+ newHeight &&
+ newHeight !== this.rootDoc.height &&
+ !this.props.dontRegisterView
+ ) {
this.props.setHeight?.(newHeight);
}
},
- { fireImmediately: true }
+ { fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
);
this._disposers.links = reaction(
() => LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
@@ -1208,6 +1237,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._disposers.selected = reaction(
() => this.props.isSelected(),
action(selected => {
+ //selected && setTimeout(() => this.prepareForTyping());
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
}
@@ -1216,7 +1246,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
if (this._editorView && selected) {
RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
- this.autoLink();
+ setTimeout(this.autoLink, 20);
}
// Accessing editor and text doc for gpt assisted text edits
if (this._editorView && selected) {
@@ -1232,9 +1262,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
() => this._recording,
() => {
this.stopDictation(true);
- if (this._recording) {
- this.recordDictation();
- }
+ this._recording && this.recordDictation();
}
);
if (this._recording) setTimeout(this.recordDictation);
@@ -1499,29 +1527,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
- } else {
- this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
}
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.props.isContentActive()) this.prepareForTyping();
if (this._editorView) {
const tr = this._editorView.state.tr;
const { from, to } = tr.selection;
// for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated.
if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
- this._editorView.dispatch(
- this._editorView.state.tr.setStoredMarks([
- ...(this._editorView.state.storedMarks ?? []),
- ...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [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)] : []),
- ])
- );
+
if (FormattedTextBox.PasteOnLoad) {
const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
FormattedTextBox.PasteOnLoad = undefined;
@@ -1531,9 +1546,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
FormattedTextBox.DontSelectInitialText = false;
}
+ // 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.
+ prepareForTyping = () => {
+ if (!this._editorView) return;
+ const docDefaultMarks = [
+ ...(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)] : []),
+ ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
+ ];
+ this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
+ };
+
+ @action
componentWillUnmount() {
+ if (this._recording) {
+ this._recording = !this._recording;
+ }
Object.values(this._disposers).forEach(disposer => disposer?.());
this.endUndoTypingBatch();
+ FormattedTextBox.LiveTextUndo?.end();
+ FormattedTextBox.LiveTextUndo = undefined;
this.unhighlightSearchTerms();
this._editorView?.destroy();
RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
@@ -1568,8 +1604,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
if (this._recording && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
- e.preventDefault();
- e.stopPropagation();
}
this._downX = e.clientX;
this._downY = e.clientY;
@@ -1807,8 +1841,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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) })));
+ if (e.code !== 'Space') {
+ [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
+ this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+ }
+ break;
}
this.startUndoTypingBatch();
};
@@ -1830,7 +1867,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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) {
+ if (children && !SnappingManager.GetIsDragging()) {
const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
const toHgt = (node: Element) => {
const { height, marginTop, marginBottom } = getComputedStyle(node);
@@ -1841,6 +1878,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this.props.setHeight && scrollHeight && !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 {
@@ -1962,12 +2000,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
cycleAlternateText = () => {
if (this.layoutDoc._layout_enableAltContentUI) {
- const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`];
+ const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`];
this.rootDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
}
};
@computed get overlayAlternateIcon() {
- const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`];
+ const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`];
return (
<Tooltip
title={
@@ -2011,19 +2049,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
};
_oldWheel: any;
+ @computed get fontColor() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+ }
+ @computed get fontSize() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize);
+ }
+ @computed get fontFamily() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily);
+ }
+ @computed get fontWeight() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight);
+ }
render() {
TraceMobx();
- const active = this.props.isContentActive() || this.props.isSelected();
- const selected = active;
const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
const rounded = StrCast(this.layoutDoc.layout_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;
+ setTimeout(() => !this.props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
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._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
- const selPaddingClass = selected && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : '';
const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
return styleFromLayoutString?.height === '0px' ? null : (
<div
@@ -2040,26 +2084,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
? {}
: {
transform: `scale(${scale})`,
- transformOrigin: 'top left',
width: `${100 / scale}%`,
height: `${100 / scale}%`,
}),
- display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
+ // display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined,
transition: 'inherit',
// overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
- color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
- fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
- fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily),
- fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight),
+ color: this.fontColor,
+ fontSize: this.fontSize,
+ fontFamily: this.fontFamily,
+ fontWeight: this.fontWeight,
...styleFromLayoutString,
}}>
<div
className="formattedTextBox-cont"
ref={this._ref}
style={{
+ cursor: this.props.isContentActive() ? 'text' : undefined,
overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox
height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
- pointerEvents: interactive ? undefined : 'none',
+ pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -2071,25 +2115,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
onPointerDown={this.onPointerDown}
onDoubleClick={this.onDoubleClick}>
<div
- className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
+ className={`formattedTextBox-outer`}
ref={this._scrollRef}
style={{
width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
onDrop={this.ondrop}>
<div
- className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+ className={`formattedTextBox-inner${rounded}`}
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() ? (IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined) : undefined,
+ paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`),
+ paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`),
+ paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
+ paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
}}
/>
</div>
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 8d57cc081..ec11079b4 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -4,8 +4,7 @@ import { Schema } from 'prosemirror-model';
import { splitListItem, wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
-import { Doc } from '../../../../fields/Doc';
-import { AclAugment, AclSelfEdit } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
import { GetEffectiveAcl } from '../../../../fields/util';
import { Utils } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
@@ -13,6 +12,7 @@ import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { OpenWhere } from '../DocumentView';
import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
+import { Doc } from '../../../../fields/Doc';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -49,15 +49,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const canEdit = (state: any) => {
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?.();
- if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) {
- return false;
- }
+ const prevNode = state.selection.$cursor.nodeBefore;
+ const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
+ if (prevUser != Doc.CurrentUserEmail) {
+ return false;
}
- break;
}
return true;
};
@@ -338,7 +334,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Command to create a blank space
bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (!canEdit(state)) return true;
+ if (props.DataDoc && GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 7c3e4baad..9c46459b0 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -104,7 +104,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
_disposer: IReactionDisposer | undefined;
componentDidMount() {
this._disposer = reaction(
- () => SelectionManager.Views(),
+ () => SelectionManager.Views().slice(),
views => this.updateMenu(undefined, undefined, undefined)
);
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ac1e7ce5d..8bafc2cef 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -243,13 +243,13 @@ export class RichTextRules {
// 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
+ // [[:docTitle]] => hyperlink
// [[fieldKey]] => show field
// [[fieldKey=value]] => show field and also set its value
- // [[fieldKey:Doc]] => show field of doc
+ // [[fieldKey:docTitle]] => 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 docId = match[3]?.replace(':', '');
+ const docTitle = match[3]?.replace(':', '');
const value = match[2]?.substring(1);
const linkToDoc = (target: Doc) => {
const rstate = this.TextBox.EditorView?.state;
@@ -266,12 +266,12 @@ export class RichTextRules {
}
};
if (!fieldKey) {
- if (docId) {
- const target = DocServer.QUERY_SERVER_CACHE(docId);
- if (target) setTimeout(() => linkToDoc(target));
- else DocServer.GetRefField(docId).then(docx => linkToDoc((docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId + '(auto)', _width: 500, _height: 500 }, docId)));
-
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ if (docTitle) {
+ const target = DocServer.FindDocByTitle(docTitle);
+ if (target) {
+ setTimeout(() => linkToDoc(target));
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ }
}
return state.tr;
}
@@ -279,8 +279,12 @@ export class RichTextRules {
const num = value.match(/^[0-9.]$/);
this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
}
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId, hideKey: false });
- return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
+ const target = DocServer.FindDocByTitle(docTitle);
+ if (target) {
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target[Id], hideKey: false });
+ return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
+ }
+ return state.tr;
}),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
new file mode 100644
index 000000000..58f0b29e4
--- /dev/null
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -0,0 +1,38 @@
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../../Utils';
+import { Transform } from '../../../util/Transform';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { DefaultStyleProvider } from '../../StyleProvider';
+import { DocumentView, DocumentViewInternal } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import React = require('react');
+
+@observer
+export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(ImportElementBox, fieldKey);
+ }
+
+ screenToLocalXf = () => this.props.ScreenToLocalTransform().scale(1 * (this.props.NativeDimScaling?.() || 1));
+ @computed get mainItem() {
+ return (
+ <div style={{ backgroundColor: 'pink' }}>
+ <DocumentView
+ {...this.props} //
+ LayoutTemplateString={undefined}
+ Document={this.rootDoc}
+ isContentActive={returnFalse}
+ DataDoc={undefined}
+ addDocument={returnFalse}
+ ScreenToLocalTransform={this.screenToLocalXf}
+ hideResizeHandles={true}
+ />
+ </div>
+ );
+ }
+ render() {
+ return !(this.rootDoc instanceof Doc) ? null : this.mainItem;
+ }
+}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 54d32bae6..b02e7ecbd 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty
import { AudioField } from '../../../../fields/URLField';
import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
-import { Docs } from '../../../documents/Documents';
+import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -76,7 +76,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
constructor(props: any) {
super(props);
if (!PresBox.navigateToDocScript) {
- PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)')!;
+ PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentation_targetDoc, self)')!;
}
}
@@ -118,7 +118,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return DocListCast(this.rootDoc[this.presFieldKey]);
}
@computed get tagDocs() {
- return this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null));
+ return this.childDocs.map(doc => Cast(doc.presentation_targetDoc, Doc, null));
}
@computed get itemIndex() {
return NumCast(this.rootDoc._itemIndex);
@@ -127,10 +127,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return DocCast(this.childDocs[NumCast(this.rootDoc._itemIndex)]);
}
@computed get targetDoc() {
- return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
+ return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
}
public static targetRenderedDoc = (doc: Doc) => {
- const targetDoc = Cast(doc?.presentationTargetDoc, Doc, null);
+ const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
return targetDoc?.layout_unrendered ? DocCast(targetDoc.annotationOn) : targetDoc;
};
@computed get scrollable() {
@@ -151,7 +151,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get selectedDoc() {
return this.selectedDocumentView?.rootDoc;
}
- isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentationTargetDoc === layoutDoc;
+ isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentation_targetDoc === layoutDoc;
clearSelectedArray = () => this.selectedArray.clear();
addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
@@ -198,18 +198,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
this.turnOffEdit(true);
this._disposers.selection = reaction(
- () => SelectionManager.Views(),
+ () => SelectionManager.Views().slice(),
views => (!PresBox.Instance || views.some(view => view.props.Document === this.rootDoc)) && this.updateCurrentPresentation(),
{ fireImmediately: true }
);
this._disposers.editing = reaction(
- () => this.layoutDoc.presStatus === PresStatus.Edit,
+ () => this.layoutDoc.presentation_status === PresStatus.Edit,
editing => {
if (editing) {
this.childDocs.forEach(doc => {
- if (doc.presIndexed !== undefined) {
+ if (doc.presentation_indexed !== undefined) {
this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
- doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
+ doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
}
});
}
@@ -226,10 +226,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
_mediaTimer!: [NodeJS.Timeout, Doc];
// 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played
startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
- const duration: number = NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime);
+ const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart);
if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
- targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.presStartTime), NumCast(activeItem.presStartTime) + duration);
+ targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.config_clipStart), NumCast(activeItem.config_clipStart) + duration);
}
};
@@ -253,12 +253,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
(nextSelected: number, force = false) =>
() => {
if (nextSelected < this.childDocs.length) {
- if (force || this.childDocs[nextSelected].groupWithUp) {
+ if (force || this.childDocs[nextSelected].presentation_groupWithUp) {
this.addToSelectedArray(this.childDocs[nextSelected]);
- const serial = nextSelected + 1 < this.childDocs.length && NumCast(this.childDocs[nextSelected + 1].groupWithUp) > 1;
+ const serial = nextSelected + 1 < this.childDocs.length && NumCast(this.childDocs[nextSelected + 1].presentation_groupWithUp) > 1;
if (serial) {
this.gotoDocument(nextSelected, this.activeItem, true, async () => {
- const waitTime = NumCast(this.activeItem.presDuration) - NumCast(this.activeItem.presTransition);
+ const waitTime = NumCast(this.activeItem.presentation_duration);
await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime)));
doGroupWithUp(nextSelected + 1)();
});
@@ -276,7 +276,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// docs within a slide target that will be progressively revealed
progressivizedItems = (doc: Doc) => {
const targetList = PresBox.targetRenderedDoc(doc);
- if (doc.presIndexed !== undefined && targetList) {
+ if (doc.presentation_indexed !== undefined && targetList) {
const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']);
return listItems.filter(doc => !doc.layout_unrendered);
}
@@ -298,7 +298,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
next = () => {
const progressiveReveal = (first: boolean) => {
- const presIndexed = Cast(this.activeItem?.presIndexed, 'number', null);
+ const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null);
if (presIndexed !== undefined) {
const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem);
targetRenderedDoc._dataTransition = 'all 1s';
@@ -311,15 +311,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const targetView = listItems && DocumentManager.Instance.getFirstDocumentView(listItemDoc);
Doc.linkFollowUnhighlight();
Doc.HighlightDoc(listItemDoc);
- listItemDoc.presEffect = this.activeItem.presBulletEffect;
- listItemDoc.presTransition = 500;
+ listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
+ listItemDoc.presentation_transition = 500;
targetView?.setAnimEffect(listItemDoc, 500);
if (targetView?.docView && this.activeItem.presBulletExpand) {
- targetView.docView._animateScalingTo = 1.1;
- Doc.AddUnHighlightWatcher(() => (targetView!.docView!._animateScalingTo = 0));
+ targetView.docView._animateScalingTo = 1.2;
+ targetView.docView._animateScaleTime = 400;
+ Doc.AddUnHighlightWatcher(() => {
+ targetView.docView!._animateScaleTime = undefined;
+ targetView!.docView!._animateScalingTo = 0;
+ });
}
listItemDoc.opacity = undefined;
- this.activeItem.presIndexed = presIndexed + 1;
+ this.activeItem.presentation_indexed = presIndexed + 1;
}
return true;
}
@@ -338,7 +342,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
progressiveReveal(true); // shows first progressive document, but without a transition effect
} else {
- if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) {
+ if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presentation_status === PresStatus.Edit)) {
// Case 2: Last slide and presLoop is toggled ON or it is in Edit mode
this.nextSlide(0);
progressiveReveal(true); // shows first progressive document, but without a transition effect
@@ -354,9 +358,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem: Doc = this.activeItem;
let prevSelected = this.itemIndex;
// Functionality for group with up
- let didZoom = activeItem.presMovement;
- for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].groupWithUp; prevSelected--) {
- didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presMovement : didZoom;
+ let didZoom = activeItem.presentation_movement;
+ for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].presentation_groupWithUp; prevSelected--) {
+ didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presentation_movement : didZoom;
}
if (activeItem && this.childDocs[this.itemIndex - 1] !== undefined) {
// Case 2: There are no other frames so it should go to the previous slide
@@ -377,20 +381,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.UnBrushAllDocs();
if (index >= 0 && index < this.childDocs.length) {
this.rootDoc._itemIndex = index;
- if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
+ if (from?.mediaStopTriggerList && this.layoutDoc.presentation_status !== PresStatus.Edit) {
DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
}
- if (from?.mediaStop === 'auto' && this.layoutDoc.presStatus !== PresStatus.Edit) {
- this.stopTempMedia(from.presentationTargetDoc);
+ if (from?.mediaStop === 'auto' && this.layoutDoc.presentation_status !== PresStatus.Edit) {
+ this.stopTempMedia(from.presentation_targetDoc);
}
// If next slide is audio / video 'Play automatically' then the next slide should be played
- if (this.layoutDoc.presStatus !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.mediaStart === 'auto') {
+ if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.mediaStart === 'auto') {
this.startTempMedia(this.targetDoc, this.activeItem);
}
if (!group) this.clearSelectedArray();
this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
this.turnOffEdit();
- this.navigateToActiveItem(finished); //Handles movement to element only when presTrail is list
+ this.navigateToActiveItem(finished); //Handles movement to element only when presentationTrail is list
this.doHideBeforeAfter(); //Handles hide after/before
}
});
@@ -414,33 +418,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
playAnnotation = (anno: AudioField) => {};
@action
- static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.presPinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
+ static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
- if (!bestTarget || activeItem === bestTarget) return;
+ if (!bestTarget) return;
let changed = false;
if (pinDocLayout) {
if (
- bestTarget.x !== NumCast(activeItem.presX, NumCast(bestTarget.x)) ||
- bestTarget.y !== NumCast(activeItem.presY, NumCast(bestTarget.y)) ||
- bestTarget.rotation !== NumCast(activeItem.presRotation, NumCast(bestTarget.rotation)) ||
- bestTarget.width !== NumCast(activeItem.presWidth, NumCast(bestTarget.width)) ||
- bestTarget.height !== NumCast(activeItem.presHeight, NumCast(bestTarget.height))
+ bestTarget.x !== NumCast(activeItem.config_x, NumCast(bestTarget.x)) ||
+ bestTarget.y !== NumCast(activeItem.config_y, NumCast(bestTarget.y)) ||
+ bestTarget.rotation !== NumCast(activeItem.config_rotation, NumCast(bestTarget.rotation)) ||
+ bestTarget.width !== NumCast(activeItem.config_width, NumCast(bestTarget.width)) ||
+ bestTarget.height !== NumCast(activeItem.config_height, NumCast(bestTarget.height))
) {
bestTarget._dataTransition = `all ${transTime}ms`;
- bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x));
- bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y));
- bestTarget.rotation = NumCast(activeItem.presRotation, NumCast(bestTarget.rotation));
- bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width));
- bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height));
+ bestTarget.x = NumCast(activeItem.config_x, NumCast(bestTarget.x));
+ bestTarget.y = NumCast(activeItem.config_y, NumCast(bestTarget.y));
+ bestTarget.rotation = NumCast(activeItem.config_rotation, NumCast(bestTarget.rotation));
+ bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
+ bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
changed = true;
}
}
- const activeFrame = activeItem.presActiveFrame ?? activeItem.presCurrentFrame;
+ const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame;
if (activeFrame !== undefined) {
- const transTime = NumCast(activeItem.presTransition, 500);
- const acontext = activeItem.presActiveFrame !== undefined ? DocCast(DocCast(activeItem.presentationTargetDoc).embedContainer) : DocCast(activeItem.presentationTargetDoc);
+ const transTime = NumCast(activeItem.presentation_transition, 500);
+ const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
if (context) {
const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
@@ -450,73 +454,73 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
}
- if ((pinDataTypes?.dataview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) {
+ if ((pinDataTypes?.dataview && activeItem.config_data !== undefined) || (!pinDataTypes && activeItem.config_data !== undefined)) {
bestTarget._dataTransition = `all ${transTime}ms`;
const fkey = Doc.LayoutFieldKey(bestTarget);
const setData = bestTargetView?.ComponentView?.setData;
- if (setData) setData(activeItem.presData);
- else Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
- bestTarget[fkey + '_usePath'] = activeItem.presUsePath;
+ if (setData) setData(activeItem.config_data);
+ else Doc.GetProto(bestTarget)[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+ bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
}
- if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.presXRange !== undefined)) {
- if (bestTarget.xRange !== activeItem.presXRange) {
- bestTarget.xRange = (activeItem.presXRange as ObjectField)?.[Copy]();
+ if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.config_xRange !== undefined)) {
+ if (bestTarget.xRange !== activeItem.config_xRange) {
+ bestTarget.xRange = (activeItem.config_xRange as ObjectField)?.[Copy]();
changed = true;
}
- if (bestTarget.yRange !== activeItem.presYRange) {
- bestTarget.yRange = (activeItem.presYRange as ObjectField)?.[Copy]();
+ if (bestTarget.yRange !== activeItem.config_yRange) {
+ bestTarget.yRange = (activeItem.config_yRange as ObjectField)?.[Copy]();
changed = true;
}
}
- if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.presClipWidth !== undefined)) {
+ if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.config_clipWidth !== undefined)) {
const fkey = '_' + Doc.LayoutFieldKey(bestTarget);
- if (bestTarget[fkey + '_clipWidth'] !== activeItem.presClipWidth) {
- bestTarget[fkey + '_clipWidth'] = activeItem.presClipWidth;
+ if (bestTarget[fkey + '_clipWidth'] !== activeItem.config_clipWidth) {
+ bestTarget[fkey + '_clipWidth'] = activeItem.config_clipWidth;
changed = true;
}
}
- if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.presStartTime !== undefined)) {
- if (bestTarget._layout_currentTimecode !== activeItem.presStartTime) {
- bestTarget._layout_currentTimecode = activeItem.presStartTime;
+ if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.config_clipStart !== undefined)) {
+ if (bestTarget._layout_currentTimecode !== activeItem.config_clipStart) {
+ bestTarget._layout_currentTimecode = activeItem.config_clipStart;
changed = true;
}
}
- if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.presFillColor !== undefined || activeItem.color !== undefined))) {
- if (bestTarget.fillColor !== activeItem.presFillColor) {
- Doc.GetProto(bestTarget).fillColor = activeItem.presFillColor;
+ if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
+ if (bestTarget.fillColor !== activeItem.config_fillColor) {
+ Doc.GetProto(bestTarget).fillColor = activeItem.config_fillColor;
changed = true;
}
- if (bestTarget.color !== activeItem.presColor) {
- Doc.GetProto(bestTarget).color = activeItem.presColor;
+ if (bestTarget.color !== activeItem.config_color) {
+ Doc.GetProto(bestTarget).color = activeItem.config_color;
changed = true;
}
if (bestTarget.width !== activeItem.width) {
- bestTarget._width = NumCast(activeItem.presWidth, NumCast(bestTarget.width));
+ bestTarget._width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
changed = true;
}
if (bestTarget.height !== activeItem.height) {
- bestTarget._height = NumCast(activeItem.presHeight, NumCast(bestTarget.height));
+ bestTarget._height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
changed = true;
}
}
- if ((pinDataTypes?.type_collection && activeItem.presViewType !== undefined) || (!pinDataTypes && activeItem.presViewType !== undefined)) {
- if (bestTarget._type_collection !== activeItem.presViewType) {
- bestTarget._type_collection = activeItem.presViewType;
+ if ((pinDataTypes?.type_collection && activeItem.config_viewType !== undefined) || (!pinDataTypes && activeItem.config_viewType !== undefined)) {
+ if (bestTarget._type_collection !== activeItem.config_viewType) {
+ bestTarget._type_collection = activeItem.config_viewType;
changed = true;
}
}
- if ((pinDataTypes?.filters && activeItem.presDocFilters !== undefined) || (!pinDataTypes && activeItem.presDocFilters !== undefined)) {
- if (bestTarget.childFilters !== activeItem.presDocFilters) {
- bestTarget.childFilters = ObjectField.MakeCopy(activeItem.presDocFilters as ObjectField) || new List<string>([]);
+ if ((pinDataTypes?.filters && activeItem.config_docFilters !== undefined) || (!pinDataTypes && activeItem.config_docFilters !== undefined)) {
+ if (bestTarget.childFilters !== activeItem.config_docFilters) {
+ bestTarget.childFilters = ObjectField.MakeCopy(activeItem.config_docFilters as ObjectField) || new List<string>([]);
changed = true;
}
}
- if ((pinDataTypes?.pivot && activeItem.presPivotField !== undefined) || (!pinDataTypes && activeItem.presPivotField !== undefined)) {
- if (bestTarget.pivotField !== activeItem.presPivotField) {
- bestTarget.pivotField = activeItem.presPivotField;
+ if ((pinDataTypes?.pivot && activeItem.config_pivotField !== undefined) || (!pinDataTypes && activeItem.config_pivotField !== undefined)) {
+ if (bestTarget.pivotField !== activeItem.config_pivotField) {
+ bestTarget.pivotField = activeItem.config_pivotField;
bestTarget._prevFilterIndex = 1; // need to revisit this...see CollectionTimeView
changed = true;
}
@@ -525,21 +529,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
changed = true;
}
- if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) {
- if (bestTarget._layout_scrollTop !== activeItem.presViewScroll) {
- bestTarget._layout_scrollTop = activeItem.presViewScroll;
+ if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.config_scrollTop !== undefined)) {
+ if (bestTarget._layout_scrollTop !== activeItem.config_scrollTop) {
+ bestTarget._layout_scrollTop = activeItem.config_scrollTop;
changed = true;
- const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
+ const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number'));
if (contentBounds) {
const dv = DocumentManager.Instance.getDocumentView(bestTarget)?.ComponentView;
- dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] });
+ dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }, transTime);
}
}
}
- if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.presAnnotations !== undefined)) {
+ if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.config_annotations !== undefined)) {
const fkey = Doc.LayoutFieldKey(bestTarget);
const oldItems = DocListCast(bestTarget[fkey + '_annotations']).filter(doc => doc.layout_unrendered);
- const newItems = DocListCast(activeItem.presAnnotations).map(doc => {
+ const newItems = DocListCast(activeItem.config_annotations).map(doc => {
doc.hidden = false;
return doc;
});
@@ -552,11 +556,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]);
Doc.GetProto(bestTarget)[fkey + '_annotations'] = newList;
}
- if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.presPinLayoutData !== undefined)) {
+ if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) {
changed = true;
const layoutField = Doc.LayoutFieldKey(bestTarget);
const transitioned = new Set<Doc>();
- StrListCast(activeItem.presPinLayoutData)
+ StrListCast(activeItem.config_pinLayoutData)
.map(str => JSON.parse(str) as { id: string; x: number; y: number; back: string; fill: string; w: number; h: number; data: string; text: string })
.forEach(async data => {
const doc = DocCast(DocServer.GetCachedRefField(data.id));
@@ -578,8 +582,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
}
- if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPanX !== undefined || activeItem.presViewScale !== undefined))) && !bestTarget._isGroup) {
- const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
+ if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget._isGroup) {
+ const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number'));
if (contentBounds) {
const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
bestTarget._freeform_panX = viewport.panX;
@@ -588,14 +592,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (dv) {
changed = true;
const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
- activeItem.presMovement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
- dv.ComponentView?.brushView?.(viewport);
+ activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
+ dv.ComponentView?.brushView?.(viewport, transTime);
}
} else {
- if (bestTarget._freeform_panX !== activeItem.presPanX || bestTarget._freeform_panY !== activeItem.presPanY || bestTarget._freeform_scale !== activeItem.presViewScale) {
- bestTarget._freeform_panX = activeItem.presPanX ?? bestTarget._freeform_panX;
- bestTarget._freeform_panY = activeItem.presPanY ?? bestTarget._freeform_panY;
- bestTarget._freeform_scale = activeItem.presViewScale ?? bestTarget._freeform_scale;
+ if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) {
+ bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX;
+ bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY;
+ bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale;
changed = true;
}
}
@@ -610,17 +614,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
/// target doc when navigating to it.
@action
static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) {
+ pinDoc.presentation = true;
+ pinDoc.config = '';
if (pinProps.pinDocLayout) {
- pinDoc.presPinLayout = true;
- pinDoc.presX = NumCast(targetDoc.x);
- pinDoc.presY = NumCast(targetDoc.y);
- pinDoc.presRotation = NumCast(targetDoc.rotation);
- pinDoc.presWidth = NumCast(targetDoc.width);
- pinDoc.presHeight = NumCast(targetDoc.height);
+ pinDoc.config_pinLayout = true;
+ pinDoc.config_x = NumCast(targetDoc.x);
+ pinDoc.config_y = NumCast(targetDoc.y);
+ pinDoc.config_rotation = NumCast(targetDoc.rotation);
+ pinDoc.config_width = NumCast(targetDoc.width);
+ pinDoc.config_height = NumCast(targetDoc.height);
}
if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true;
if (pinProps.pinData) {
- pinDoc.presPinData =
+ pinDoc.config_pinData =
pinProps.pinData.scrollable ||
pinProps.pinData.temporal ||
pinProps.pinData.pannable ||
@@ -632,30 +638,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
pinProps?.activeFrame !== undefined;
const fkey = Doc.LayoutFieldKey(targetDoc);
if (pinProps.pinData.dataview) {
- pinDoc.presUsePath = targetDoc[fkey + '_usePath'];
- pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
+ pinDoc.config_usePath = targetDoc[fkey + '_usePath'];
+ pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
}
if (pinProps.pinData.dataannos) {
const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
+ pinDoc.config_annotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
}
if (pinProps.pinData.inkable) {
- pinDoc.presFillColor = targetDoc.fillColor;
- pinDoc.presColor = targetDoc.color;
- pinDoc.presWidth = targetDoc._width;
- pinDoc.presHeight = targetDoc._height;
+ pinDoc.config_fillColor = targetDoc.fillColor;
+ pinDoc.config_color = targetDoc.color;
+ pinDoc.config_width = targetDoc._width;
+ pinDoc.config_height = targetDoc._height;
}
- if (pinProps.pinData.scrollable) pinDoc.presViewScroll = targetDoc._layout_scrollTop;
+ if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
if (pinProps.pinData.clippable) {
const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.presClipWidth = targetDoc[fkey + '_clipWidth'];
+ pinDoc.config_clipWidth = targetDoc[fkey + '_clipWidth'];
}
if (pinProps.pinData.datarange) {
- pinDoc.presXRange = undefined; //targetDoc?.xrange;
- pinDoc.presYRange = undefined; //targetDoc?.yrange;
+ pinDoc.config_xRange = undefined; //targetDoc?.xrange;
+ pinDoc.config_yRange = undefined; //targetDoc?.yrange;
}
if (pinProps.pinData.poslayoutview)
- pinDoc.presPinLayoutData = new List<string>(
+ pinDoc.config_pinLayoutData = new List<string>(
DocListCast(targetDoc[fkey] as ObjectField).map(d =>
JSON.stringify({
id: d[Id],
@@ -670,28 +676,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
})
)
);
- if (pinProps.pinData.type_collection) pinDoc.presViewType = targetDoc._type_collection;
- if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField);
- if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField;
+ if (pinProps.pinData.type_collection) pinDoc.config_viewType = targetDoc._type_collection;
+ if (pinProps.pinData.filters) pinDoc.config_docFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField);
+ if (pinProps.pinData.pivot) pinDoc.config_pivotField = targetDoc._pivotField;
if (pinProps.pinData.pannable) {
- pinDoc.presPanX = NumCast(targetDoc._freeform_panX);
- pinDoc.presPanY = NumCast(targetDoc._freeform_panY);
- pinDoc.presViewScale = NumCast(targetDoc._freeform_scale, 1);
+ pinDoc.config_panX = NumCast(targetDoc._freeform_panX);
+ pinDoc.config_panY = NumCast(targetDoc._freeform_panY);
+ pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
}
if (pinProps.pinData.temporal) {
- pinDoc.presStartTime = targetDoc._layout_currentTimecode;
- const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.presStartTime) + 0.1);
- pinDoc.presEndTime = NumCast(targetDoc.clipEnd, duration);
+ pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
+ const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
+ pinDoc.config_clipEnd = NumCast(targetDoc.clipEnd, duration);
}
}
if (pinProps?.pinViewport) {
// If pinWithView option set then update scale and x / y props of slide
const bounds = pinProps.pinViewport;
- pinDoc.presPinView = true;
- pinDoc.presViewScale = NumCast(targetDoc._freeform_scale, 1);
- pinDoc.presPanX = bounds.left + bounds.width / 2;
- pinDoc.presPanY = bounds.top + bounds.height / 2;
- pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
+ pinDoc.config_pinView = true;
+ pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
+ pinDoc.config_panX = bounds.left + bounds.width / 2;
+ pinDoc.config_panY = bounds.top + bounds.height / 2;
+ pinDoc.config_viewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
}
}
/**
@@ -729,23 +735,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) {
- if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
+ if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
(DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
return;
}
- const effect = activeItem.presEffect && activeItem.presEffect !== PresEffect.None ? activeItem.presEffect : undefined;
- const presTime = NumCast(activeItem.presTransition, effect ? 750 : 500);
+ const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined;
+ const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500);
const options: DocFocusOptions = {
- willPan: activeItem.presMovement !== PresMovement.None,
- willZoomCentered: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump || activeItem.presMovement === PresMovement.Center,
- zoomScale: activeItem.presMovement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1),
- zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime),
+ willPan: activeItem.presentation_movement !== PresMovement.None,
+ willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center,
+ zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1),
+ zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime),
effect: activeItem,
noSelect: true,
openLocation: OpenWhere.addLeft,
anchorDoc: activeItem,
easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
- zoomTextSelections: BoolCast(activeItem.presZoomText),
+ zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
playAudio: BoolCast(activeItem.presPlayAudio),
};
if (activeItem.presOpenInLightbox) {
@@ -755,7 +761,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
}
if (targetDoc) {
- if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[Animation] = undefined;
+ if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined;
DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => {
// if target or the doc it annotates is not in the lightbox, then close the lightbox
@@ -778,16 +784,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const tagDoc = PresBox.targetRenderedDoc(curDoc);
const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined;
- if (curDoc.presHide) {
+ if (curDoc.presentation_hide) {
if (index !== this.itemIndex) {
opacity = 1;
}
}
const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
- if (curDoc.presHideBefore && index === hidingIndBef) {
+ if (curDoc.presentation_hideBefore && index === hidingIndBef) {
if (index > this.itemIndex) {
opacity = 0;
- } else if (index === this.itemIndex || !curDoc.presHideAfter) {
+ } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
opacity = 1;
setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
}
@@ -797,15 +803,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
.slice()
.reverse()
.find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
- if (curDoc.presHideAfter && index === hidingIndAft) {
+ if (curDoc.presentation_hideAfter && index === hidingIndAft) {
if (index < this.itemIndex) {
opacity = 0;
- } else if (index === this.itemIndex || !curDoc.presHideBefore) {
+ } else if (index === this.itemIndex || !curDoc.presentation_hideBefore) {
opacity = 1;
}
}
const hidingInd = itemIndexes.find(item => item === this.itemIndex);
- if (curDoc.presHide && index === hidingInd) {
+ if (curDoc.presentation_hide && index === hidingInd) {
if (index === this.itemIndex) {
opacity = 0;
}
@@ -863,9 +869,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// The function pauses the auto presentation
@action
pauseAutoPres = () => {
- if (this.layoutDoc.presStatus === PresStatus.Autoplay) {
+ if (this.layoutDoc.presentation_status === PresStatus.Autoplay) {
if (this._presTimer) clearTimeout(this._presTimer);
- this.layoutDoc.presStatus = PresStatus.Manual;
+ this.layoutDoc.presentation_status = PresStatus.Manual;
this.childDocs.forEach(this.stopTempMedia);
}
};
@@ -896,23 +902,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
runInAction(() => (this._expandBoolean = !this._expandBoolean));
this.rootDoc.expandBoolean = this._expandBoolean;
this.childDocs.forEach(doc => {
- doc.presExpandInlineButton = this._expandBoolean;
+ doc.presentation_expandInlineButton = this._expandBoolean;
});
};
initializePresState = (startIndex: number) => {
this.childDocs.forEach((doc, index) => {
const tagDoc = PresBox.targetRenderedDoc(doc);
- if (doc.presHideBefore && index > startIndex) tagDoc.opacity = 0;
- if (doc.presHideAfter && index < startIndex) tagDoc.opacity = 0;
- if (doc.presIndexed !== undefined && index >= startIndex) {
- const startInd = NumCast(doc.presIndexedStart);
+ if (doc.presentation_hideBefore && index > startIndex) tagDoc.opacity = 0;
+ if (doc.presentation_hideAfter && index < startIndex) tagDoc.opacity = 0;
+ if (doc.presentation_indexed !== undefined && index >= startIndex) {
+ const startInd = NumCast(doc.presentation_indexedStart);
this.progressivizedItems(doc)
?.slice(startInd)
.forEach(indexedDoc => (indexedDoc.opacity = 0));
- doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd);
+ doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd);
}
- // if (doc.presHide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0;
+ // if (doc.presentation_hide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0;
});
};
@@ -926,13 +932,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
PresBox.Instance = this;
clearTimeout(this._presTimer);
if (this.childDocs.length) {
- this.layoutDoc.presStatus = PresStatus.Autoplay;
+ this.layoutDoc.presentation_status = PresStatus.Autoplay;
this.initializePresState(startIndex);
const func = () => {
- const delay = NumCast(this.activeItem.presDuration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presTransition);
+ const delay = NumCast(this.activeItem.presentation_duration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presentation_transition);
this._presTimer = setTimeout(() => {
- if (!this.next()) this.layoutDoc.presStatus = this._exitTrail?.() ?? PresStatus.Manual;
- this.layoutDoc.presStatus === PresStatus.Autoplay && func();
+ if (!this.next()) this.layoutDoc.presentation_status = this._exitTrail?.() ?? PresStatus.Manual;
+ this.layoutDoc.presentation_status === PresStatus.Autoplay && func();
}, delay);
};
this.gotoDocument(startIndex, this.activeItem, undefined, func);
@@ -966,7 +972,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
doc._width = PresBox.minimizedWidth;
Doc.AddToMyOverlay(doc);
PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex);
- return (doc.presStatus = PresStatus.Manual);
+ return (doc.presentation_status = PresStatus.Manual);
}
/**
@@ -1010,45 +1016,42 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
movementName = action((activeItem: Doc) => {
- if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) {
+ if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presentation_movement) as any)) {
return PresMovement.Zoom;
}
- return StrCast(activeItem.presMovement);
+ return StrCast(activeItem.presentation_movement);
});
whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
// For dragging documents into the presentation trail
addDocumentFilter = (docs: Doc[]) => {
docs.forEach((doc, i) => {
- if (doc.presentationTargetDoc) return true;
+ if (doc.presentation_targetDoc) return true;
if (doc.type === DocumentType.LABEL) {
const audio = Cast(doc.annotationOn, Doc, null);
if (audio) {
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;
+ audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
+ audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
+ audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd;
TabDocView.PinDoc(audio, { audioRange: true });
setTimeout(() => this.removeDocument(doc), 0);
return false;
}
} else {
- if (!doc.presentationTargetDoc) doc.title = doc.title + ' - Slide';
- doc.presentationTargetDoc = doc.createdFrom; // dropped document will be a new embedding of an embedded document somewhere else.
- doc.presMovement = PresMovement.Zoom;
- if (this._expandBoolean) doc.presExpandInlineButton = true;
+ if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
+ doc.presentation_targetDoc = doc.createdFrom; // dropped document will be a new embedding of an embedded document somewhere else.
+ doc.presentation_movement = PresMovement.Zoom;
+ if (this._expandBoolean) doc.presentation_expandInlineButton = true;
}
});
return true;
};
- childLayoutTemplate = () => (!this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement));
+ childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
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) => this.props.isContentActive(outsideReaction);
- //.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.
*/
@@ -1060,7 +1063,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get listOfSelected() {
return Array.from(this.selectedArray).map((doc: Doc, index: any) => {
const curDoc = Cast(doc, Doc, null);
- const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
+ const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
if (curDoc && curDoc === this.activeItem)
return (
<div key={index} className="selectedList-items">
@@ -1166,7 +1169,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (anchorNode && anchorNode.className?.includes('lm_title')) return;
switch (e.key) {
case 'Backspace':
- if (this.layoutDoc.presStatus === 'edit') {
+ if (this.layoutDoc.presentation_status === 'edit') {
undoBatch(
action(() => {
for (const doc of this.selectedArray) {
@@ -1183,11 +1186,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
case 'Escape':
if (Doc.IsInMyOverlay(this.layoutDoc)) {
this.exitClicked();
- } else if (this.layoutDoc.presStatus === PresStatus.Edit) {
+ } else if (this.layoutDoc.presentation_status === PresStatus.Edit) {
this.clearSelectedArray();
this._eleArray.length = this._dragArray.length = 0;
} else {
- this.layoutDoc.presStatus = PresStatus.Edit;
+ this.layoutDoc.presentation_status = PresStatus.Edit;
}
if (this._presTimer) clearTimeout(this._presTimer);
handled = true;
@@ -1204,7 +1207,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.next();
if (this._presTimer) {
clearTimeout(this._presTimer);
- this.layoutDoc.presStatus = PresStatus.Manual;
+ this.layoutDoc.presentation_status = PresStatus.Manual;
}
}
handled = true;
@@ -1221,19 +1224,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.back();
if (this._presTimer) {
clearTimeout(this._presTimer);
- this.layoutDoc.presStatus = PresStatus.Manual;
+ this.layoutDoc.presentation_status = PresStatus.Manual;
}
}
handled = true;
break;
case 'Spacebar':
case ' ':
- if (this.layoutDoc.presStatus === PresStatus.Manual) this.startOrPause(true);
- else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer);
+ if (this.layoutDoc.presentation_status === PresStatus.Manual) this.startOrPause(true);
+ else if (this.layoutDoc.presentation_status === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer);
handled = true;
break;
case 'a':
- if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === 'edit') {
+ if ((e.metaKey || e.altKey) && this.layoutDoc.presentation_status === 'edit') {
this.clearSelectedArray();
this.childDocs.forEach(doc => this.addToSelectedArray(doc));
handled = true;
@@ -1256,9 +1259,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement();
const dv = DocumentManager.Instance.getDocumentView(presCollection);
this.childDocs
- .filter(doc => Cast(doc.presentationTargetDoc, Doc, null))
+ .filter(doc => Cast(doc.presentation_targetDoc, Doc, null))
.forEach((doc, index) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const tagDoc = Cast(doc.presentation_targetDoc, Doc, null);
const srcContext = Cast(tagDoc.embedContainer, Doc, null);
const width = NumCast(tagDoc._width) / 10;
const height = Math.max(NumCast(tagDoc._height) / 10, 15);
@@ -1291,17 +1294,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
);
}
- } else if (doc.presPinView && presCollection === tagDoc && dv) {
+ } else if (doc.config_pinView && presCollection === tagDoc && dv) {
// Case B: Document is presPinView and is presCollection
- const scale: number = 1 / NumCast(doc.presViewScale);
+ const scale: number = 1 / NumCast(doc.config_viewScale);
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.presPanX) - width / 2;
- const yLoc: number = NumCast(doc.presPanY) - height / 2;
+ const xLoc: number = NumCast(doc.config_panX) - width / 2;
+ const yLoc: number = NumCast(doc.config_panY) - height / 2;
docs.push(tagDoc);
order.push(
<>
@@ -1327,15 +1330,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get paths() {
let pathPoints = '';
this.childDocs.forEach((doc, index) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const tagDoc = Cast(doc.presentation_targetDoc, Doc, null);
if (tagDoc) {
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) {
- const n1x = NumCast(doc.presPanX);
- const n1y = NumCast(doc.presPanY);
+ } else if (doc.config_pinView) {
+ const n1x = NumCast(doc.config_panX);
+ const n1y = NumCast(doc.config_panY);
if ((index = 0)) pathPoints = n1x + ',' + n1y;
else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
}
@@ -1359,7 +1362,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
getPaths = (collection: Doc) => this.paths; // needs to be smarter and figure out the paths to draw for this specific collection. or better yet, draw everything in an overlay layer instad of within a collection
- // Converts seconds to ms and updates presTransition
+ // Converts seconds to ms and updates presentation_transition
public static SetTransitionTime = (number: String, setter: (timeInMS: number) => void, change?: number) => {
let timeInMS = Number(number) * 1000;
if (change) timeInMS += change;
@@ -1370,10 +1373,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
updateTransitionTime = (number: String, change?: number) => {
- PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presTransition = timeInMS)), change);
+ PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presentation_transition = timeInMS)), change);
};
- // Converts seconds to ms and updates presTransition
+ // Converts seconds to ms and updates presentation_transition
@undoBatch
updateZoom = (number: String, change?: number) => {
let scale = Number(number) / 100;
@@ -1384,7 +1387,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
/*
- * Converts seconds to ms and updates presDuration
+ * Converts seconds to ms and updates presentation_duration
*/
@undoBatch
updateDurationTime = (number: String, change?: number) => {
@@ -1392,31 +1395,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (change) timeInMS += change;
if (timeInMS < 100) timeInMS = 100;
if (timeInMS > 20000) timeInMS = 20000;
- this.selectedArray.forEach(doc => (doc.presDuration = timeInMS));
+ this.selectedArray.forEach(doc => (doc.presentation_duration = timeInMS));
};
@undoBatch
- updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presMovement = movement)));
+ updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));
@undoBatch
@action
updateHideBefore = (activeItem: Doc) => {
- activeItem.presHideBefore = !activeItem.presHideBefore;
- this.selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore));
+ activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
+ this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
};
@undoBatch
@action
updateHide = (activeItem: Doc) => {
- activeItem.presHide = !activeItem.presHide;
- this.selectedArray.forEach(doc => (doc.presHide = activeItem.presHide));
+ activeItem.presentation_hide = !activeItem.presentation_hide;
+ this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
};
@undoBatch
@action
updateHideAfter = (activeItem: Doc) => {
- activeItem.presHideAfter = !activeItem.presHideAfter;
- this.selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter));
+ activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
+ this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
};
@undoBatch
@@ -1435,11 +1438,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
@action
- updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffectDirection = effect));
+ updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect));
@undoBatch
@action
- updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presEffect = effect)));
+ updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));
static _sliderBatch: any;
public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
@@ -1469,16 +1472,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
@action
applyTo = (array: Doc[]) => {
- this.updateMovement(this.activeItem.presMovement as PresMovement, true);
- this.updateEffect(this.activeItem.presEffect as PresEffect, false, true);
+ this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
+ this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
- this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true);
- const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem;
+ this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
+ const { presentation_transition, presentation_duration, presentation_hideBefore, presentation_hideAfter } = this.activeItem;
array.forEach(curDoc => {
- curDoc.presTransition = presTransition;
- curDoc.presDuration = presDuration;
- curDoc.presHideBefore = presHideBefore;
- curDoc.presHideAfter = presHideAfter;
+ curDoc.presentation_transition = presentation_transition;
+ curDoc.presentation_duration = presentation_duration;
+ curDoc.presentation_hideBefore = presentation_hideBefore;
+ curDoc.presentation_hideAfter = presentation_hideAfter;
});
};
@@ -1486,24 +1489,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem = this.activeItem;
if (activeItem && this.targetDoc) {
const targetType = this.targetDoc.type;
- let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 0;
+ let duration = activeItem.presentation_duration ? NumCast(activeItem.presentation_duration) / 1000 : 0;
if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
return (
<div className="presBox-ribbon">
<div className="ribbon-doubleButton">
<Tooltip title={<div className="dash-tooltip">{'Hide before presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}>
+ <div className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}>
Hide before
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presHide ? 'active' : ''}`} onClick={() => this.updateHide(activeItem)}>
+ <div className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`} onClick={() => this.updateHide(activeItem)}>
Hide
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
- <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}>
+ <div className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}>
Hide after
</div>
</Tooltip>
@@ -1552,7 +1555,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (activeItem && this.targetDoc) {
const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
const bulletEffect = (effect: PresEffect) => (
- <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect, true)}>
+ <div
+ className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
+ onPointerDown={StopEvent}
+ onClick={() => this.updateEffect(effect, true)}>
{effect}
</div>
);
@@ -1565,25 +1571,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{ margin: 10 }}
type="checkbox"
onChange={() => {
- activeItem.presIndexed = activeItem.presIndexed === undefined ? 0 : undefined;
- activeItem.presHideBefore = activeItem.presIndexed !== undefined;
+ activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
+ activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined;
const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type;
- activeItem.presIndexedStart = type === DocumentType.COL ? 1 : 0;
+ activeItem.presentation_indexedStart = type === DocumentType.COL ? 1 : 0;
// a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
// to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list.
let dataField = Doc.LayoutFieldKey(tagDoc);
if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations';
- if (DocCast(activeItem.presentationTargetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc.annotationOn["${dataField}"]`);
- else activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc["${dataField}"]`);
+ if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn["${dataField}"]`);
+ else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc["${dataField}"]`);
}}
- checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false}
+ checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
/>
</div>
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Progressivize First Bullet</div>
- <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presIndexedStart = activeItem.presIndexedStart ? 0 : 1)} checked={!NumCast(activeItem.presIndexedStart)} />
+ <input
+ className="presBox-checkbox"
+ style={{ margin: 10 }}
+ type="checkbox"
+ onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)}
+ checked={!NumCast(activeItem.presentation_indexedStart)}
+ />
</div>
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Expand Current Bullet</div>
@@ -1618,18 +1630,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get transitionDropdown() {
const activeItem = this.activeItem;
- const presEffect = (effect: PresEffect) => (
- <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect, false)}>
+ const preseEffect = (effect: PresEffect) => (
+ <div
+ className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
+ onPointerDown={StopEvent}
+ onClick={() => this.updateEffect(effect, false)}>
{effect}
</div>
);
const presMovement = (movement: PresMovement) => (
- <div className={`presBox-dropdownOption ${activeItem.presMovement === movement ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateMovement(movement)}>
+ <div className={`presBox-dropdownOption ${activeItem.presentation_movement === movement ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateMovement(movement)}>
{movement}
</div>
);
const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
- const color = activeItem.presEffectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presEffectDirection) ? Colors.LIGHT_BLUE : 'black';
+ const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? Colors.LIGHT_BLUE : 'black';
return (
<Tooltip title={<div className="dash-tooltip">{direction}</div>}>
<div
@@ -1641,12 +1656,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
};
if (activeItem && this.targetDoc) {
- const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5;
+ const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5;
const zoom = NumCast(activeItem.presZoom, 1) * 100;
- const effect = activeItem.presEffect ? activeItem.presEffect : PresMovement.None;
+ const effect = activeItem.presentation_effect ? activeItem.presentation_effect : PresMovement.None;
return (
<div
- className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`}
+ className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
onPointerDown={StopEvent}
onPointerUp={StopEvent}
onClick={action(e => {
@@ -1674,7 +1689,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{presMovement(PresMovement.Jump)}
</div>
</div>
- <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
<div className="presBox-subheading">Zoom (% screen filled)</div>
<div className="ribbon-property">
<input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
@@ -1688,7 +1703,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
</div>
- {PresBox.inputter('0', '1', '100', zoom, activeItem.presMovement === PresMovement.Zoom, this.updateZoom)}
+ {PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Transition Time</div>
<div className="ribbon-property">
@@ -1718,7 +1733,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
<div className="presBox-subheading">Zoom Text Selections</div>
- <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presZoomText = !BoolCast(activeItem.presZoomText))} checked={BoolCast(activeItem.presZoomText)} />
+ <input
+ className="presBox-checkbox"
+ style={{ margin: 10 }}
+ type="checkbox"
+ onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))}
+ checked={BoolCast(activeItem.presentation_zoomText)}
+ />
</div>
<div
className="presBox-dropdown"
@@ -1730,17 +1751,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{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()}>
- {presEffect(PresEffect.None)}
- {presEffect(PresEffect.Fade)}
- {presEffect(PresEffect.Flip)}
- {presEffect(PresEffect.Rotate)}
- {presEffect(PresEffect.Bounce)}
- {presEffect(PresEffect.Roll)}
+ {preseEffect(PresEffect.None)}
+ {preseEffect(PresEffect.Fade)}
+ {preseEffect(PresEffect.Flip)}
+ {preseEffect(PresEffect.Rotate)}
+ {preseEffect(PresEffect.Bounce)}
+ {preseEffect(PresEffect.Roll)}
</div>
</div>
<div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}>
<div className="presBox-subheading">Effect direction</div>
- <div className="ribbon-property">{StrCast(this.activeItem.presEffectDirection)}</div>
+ <div className="ribbon-property">{StrCast(this.activeItem.presentation_effectDirection)}</div>
</div>
<div className="effectDirection" style={{ display: effect === PresEffectDirection.None ? 'none' : 'grid', width: 40 }}>
{presDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})}
@@ -1780,10 +1801,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
readOnly={true}
- value={NumCast(activeItem.presStartTime).toFixed(2)}
+ value={NumCast(activeItem.config_clipStart).toFixed(2)}
onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- activeItem.presStartTime = Number(e.target.value);
+ activeItem.config_clipStart = Number(e.target.value);
})}
/>
</div>
@@ -1793,7 +1814,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Duration (s)
</div>
<div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}>
- {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10}
+ {Math.round((NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart)) * 10) / 10}
</div>
</div>
<div className="slider-block">
@@ -1807,9 +1828,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
readOnly={true}
- value={NumCast(activeItem.presEndTime).toFixed(2)}
+ value={NumCast(activeItem.config_clipEnd).toFixed(2)}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- activeItem.presEndTime = Number(e.target.value);
+ activeItem.config_clipEnd = Number(e.target.value);
})}
/>
</div>
@@ -1821,12 +1842,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
step="0.1"
min={clipStart}
max={clipEnd}
- value={NumCast(activeItem.presEndTime)}
+ value={NumCast(activeItem.config_clipEnd)}
style={{ gridColumn: 1, gridRow: 1 }}
className={`toolbar-slider ${'end'}`}
id="toolbar-slider"
onPointerDown={e => {
- this._batch = UndoManager.StartBatch('presEndTime');
+ this._batch = UndoManager.StartBatch('config_clipEnd');
const endBlock = document.getElementById('endTime');
if (endBlock) {
endBlock.style.color = Colors.LIGHT_GRAY;
@@ -1844,7 +1865,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
- activeItem.presEndTime = Number(e.target.value);
+ activeItem.config_clipEnd = Number(e.target.value);
}}
/>
<input
@@ -1852,12 +1873,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
step="0.1"
min={clipStart}
max={clipEnd}
- value={NumCast(activeItem.presStartTime)}
+ value={NumCast(activeItem.config_clipStart)}
style={{ gridColumn: 1, gridRow: 1 }}
className={`toolbar-slider ${'start'}`}
id="toolbar-slider"
onPointerDown={e => {
- this._batch = UndoManager.StartBatch('presStartTime');
+ this._batch = UndoManager.StartBatch('config_clipStart');
const startBlock = document.getElementById('startTime');
if (startBlock) {
startBlock.style.color = Colors.LIGHT_GRAY;
@@ -1875,7 +1896,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();
- activeItem.presStartTime = Number(e.target.value);
+ activeItem.config_clipStart = Number(e.target.value);
}}
/>
</div>
@@ -1936,7 +1957,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<div
className="presBox-toolbar-dropdown"
- style={{ display: this._newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }}
+ style={{ display: this._newDocumentTools && this.layoutDoc.presentation_status === 'edit' ? 'inline-flex' : 'none' }}
onClick={e => e.stopPropagation()}
onPointerUp={e => e.stopPropagation()}
onPointerDown={e => e.stopPropagation()}>
@@ -2073,10 +2094,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (doc) {
const tabMap = CollectionDockingView.Instance?.tabMap;
const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
- const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentationTargetDoc ?? tab;
+ const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? tab;
const data = Cast(presCollection?.data, listSpec(Doc));
- const presData = Cast(this.rootDoc.data, listSpec(Doc));
- if (data && presData) {
+ const config_data = Cast(this.rootDoc.data, listSpec(Doc));
+ if (data && config_data) {
data.push(doc);
TabDocView.PinDoc(doc, {});
this.gotoDocument(this.childDocs.length, this.activeItem);
@@ -2125,7 +2146,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
className="dropdown-play-button"
onClick={undoBatch(
action(() => {
- this.layoutDoc.presStatus = 'manual';
+ this.layoutDoc.presentation_status = 'manual';
this.initializePresState(this.itemIndex);
this.turnOffEdit(true);
this.gotoDocument(this.itemIndex, this.activeItem);
@@ -2201,7 +2222,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
className={`presBox-buttons${Doc.IsInMyOverlay(this.rootDoc) ? ' inOverlay' : ''}`}
style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, 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}>
+ <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presentation_status === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}>
<option onPointerDown={StopEvent} value={CollectionViewType.Stacking}>
List
</option>
@@ -2216,12 +2237,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</select>
)}
<div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}>
- <span className={`presBox-button ${this.layoutDoc.presStatus === PresStatus.Edit ? 'present' : ''}`}>
+ <span className={`presBox-button ${this.layoutDoc.presentation_status === PresStatus.Edit ? 'present' : ''}`}>
<div
className="presBox-button-left"
onClick={undoBatch(() => {
if (this.childDocs.length) {
- this.layoutDoc.presStatus = 'manual';
+ this.layoutDoc.presentation_status = 'manual';
this.initializePresState(this.itemIndex);
this.gotoDocument(this.itemIndex, this.activeItem);
}
@@ -2247,12 +2268,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get playButtons() {
- const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
+ const presEnd =
+ !this.layoutDoc.presLoop &&
+ this.itemIndex === this.childDocs.length - 1 &&
+ (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
const inOverlay = Doc.IsInMyOverlay(this.rootDoc);
// 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' }}>
+ <div className="presPanelOverlay" style={{ display: this.layoutDoc.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}>
<Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
<div
className="presPanel-button"
@@ -2275,7 +2299,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.back();
if (this._presTimer) {
clearTimeout(this._presTimer);
- this.layoutDoc.presStatus = PresStatus.Manual;
+ this.layoutDoc.presentation_status = PresStatus.Manual;
}
e.stopPropagation();
},
@@ -2285,9 +2309,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}>
<FontAwesomeIcon icon={'arrow-left'} />
</div>
- <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
+ <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
<div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.startOrPause(true), false, false)}>
- <FontAwesomeIcon color={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'red' : undefined} icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'pause' : 'play'} />
+ <FontAwesomeIcon color={this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'red' : undefined} icon={this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'pause' : 'play'} />
</div>
</Tooltip>
<div
@@ -2303,7 +2327,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.next();
if (this._presTimer) {
clearTimeout(this._presTimer);
- this.layoutDoc.presStatus = PresStatus.Manual;
+ this.layoutDoc.presentation_status = PresStatus.Manual;
}
e.stopPropagation();
},
@@ -2336,7 +2360,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
<div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
{inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
- {this.activeItem?.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
+ {this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
<div className="presPanel-divider"></div>
{this.props.PanelWidth() > 250 ? (
@@ -2344,7 +2368,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
className="presPanel-button-text"
onClick={undoBatch(
action(() => {
- this.layoutDoc.presStatus = PresStatus.Edit;
+ this.layoutDoc.presentation_status = PresStatus.Edit;
clearTimeout(this._presTimer);
})
)}>
@@ -2362,7 +2386,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
startOrPause = (makeActive = true) => {
makeActive && this.updateCurrentPresentation();
- if (!this.layoutDoc.presStatus || this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startPresentation(this.itemIndex);
+ if (!this.layoutDoc.presentation_status || this.layoutDoc.presentation_status === PresStatus.Manual || this.layoutDoc.presentation_status === PresStatus.Edit) this.startPresentation(this.itemIndex);
else this.pauseAutoPres();
};
@@ -2371,7 +2395,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.back();
if (this._presTimer) {
clearTimeout(this._presTimer);
- this.layoutDoc.presStatus = PresStatus.Manual;
+ this.layoutDoc.presentation_status = PresStatus.Manual;
}
};
@@ -2380,19 +2404,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.next();
if (this._presTimer) {
clearTimeout(this._presTimer);
- this.layoutDoc.presStatus = PresStatus.Manual;
+ this.layoutDoc.presentation_status = PresStatus.Manual;
}
};
@undoBatch
@action
exitClicked = () => {
- this.layoutDoc.presStatus = this._exitTrail?.() ?? this.exitMinimize();
+ this.layoutDoc.presentation_status = this._exitTrail?.() ?? this.exitMinimize();
clearTimeout(this._presTimer);
};
AddToMap = (treeViewDoc: Doc, index: number[]) => {
- if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
+ if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
var indexNum = 0;
for (let i = 0; i < index.length; i++) {
indexNum += index[i] * 10 ** -i;
@@ -2408,20 +2432,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
RemFromMap = (treeViewDoc: Doc, index: number[]) => {
- if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
+ if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
if (!this._unmounting && this.isTree) {
this._treeViewMap.delete(treeViewDoc);
this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
}
};
- 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]);
+ sort = (treeView_Map: Map<Doc, number>) => [...treeView_Map.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
render() {
// needed to ensure that the childDocs are loaded for looking up fields
this.childDocs.slice();
const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
- const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
+ const presEnd =
+ !this.layoutDoc.presLoop &&
+ this.itemIndex === this.childDocs.length - 1 &&
+ (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
<div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
@@ -2440,9 +2467,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<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>}>
+ <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
<div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}>
- <FontAwesomeIcon icon={this.layoutDoc.presStatus === 'auto' ? 'pause' : 'play'} />
+ <FontAwesomeIcon icon={this.layoutDoc.presentation_status === '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)}>
@@ -2456,7 +2483,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
<div className="presPanel-button-text">
Slide {this.itemIndex + 1}
- {this.activeItem?.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
+ {this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
</div>
<div className="presPanel-divider" />
<div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>
@@ -2479,9 +2506,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
childIgnoreNativeSize={true}
moveDocument={returnFalse}
ignoreUnrendered={true}
+ childDragAction="move"
+ setContentView={emptyFunction}
//childLayoutFitWidth={returnTrue}
childOpacity={returnOne}
- //childLayoutString={PresElementBox.LayoutString('data')}
childClickScript={PresBox.navigateToDocScript}
childLayoutTemplate={this.childLayoutTemplate}
childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index a98db3d66..0285dac50 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -59,13 +59,13 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
}
@computed get targetDoc() {
- return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc;
+ return Cast(this.rootDoc.presentation_targetDoc, Doc, null) || this.rootDoc;
}
componentDidMount() {
this.layoutDoc.layout_hideLinkButton = true;
this._heightDisposer = reaction(
- () => ({ expand: this.rootDoc.presExpandInlineButton, height: this.collapsedHeight }),
+ () => ({ expand: this.rootDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
{ fireImmediately: true }
);
@@ -80,7 +80,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
@action
- presExpandDocumentClick = () => (this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton);
+ presExpandDocumentClick = () => (this.rootDoc.presentation_expandInlineButton = !this.rootDoc.presentation_expandInlineButton);
embedHeight = (): number => this.collapsedHeight + this.expandViewHeight;
// embedWidth = () => this.props.PanelWidth();
@@ -95,7 +95,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
* presentation element.
*/
@computed get renderEmbeddedInline() {
- return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
+ return !this.rootDoc.presentation_expandInlineButton || !this.targetDoc ? null : (
<div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
<DocumentView
Document={PresBox.targetRenderedDoc(this.rootDoc)}
@@ -159,9 +159,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get transition() {
let transitionInS: number;
- if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000;
+ if (this.rootDoc.presentation_transition) transitionInS = NumCast(this.rootDoc.presentation_transition) / 1000;
else transitionInS = 0.5;
- return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
+ return this.rootDoc.presentation_movement === PresMovement.Jump || this.rootDoc.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
}
private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -193,13 +193,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dragArray = this.presBoxView?._dragArray ?? [];
const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
- dragData.dropAction = 'move';
dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
dragData.moveDocument = this.props.moveDocument;
const dragItem: HTMLElement[] = [];
+ const classesToRestore = new Map<HTMLElement, string>();
if (dragArray.length === 1) {
const doc = this._itemRef.current || dragArray[0];
if (doc) {
+ classesToRestore.set(doc, doc.className);
doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide';
dragItem.push(doc);
}
@@ -213,16 +214,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
dragItem.push(doc);
}
- // const dropEvent = () => runInAction(() => this._dragging = false);
if (activeItem) {
+ runInAction(() => (this._dragging = true));
DragManager.StartDocumentDrag(
dragItem.map(ele => ele),
dragData,
e.clientX,
e.clientY,
- undefined
+ undefined,
+ action(() => {
+ Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
+ this._dragging = false;
+ })
);
- // runInAction(() => this._dragging = true);
return true;
}
return false;
@@ -235,7 +239,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
onPointerMove = (e: PointerEvent) => {
const slide = this._itemRef.current!;
- const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentationTargetDoc);
+ const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentation_targetDoc);
if (slide && dragIsPresItem) {
const rect = slide.getBoundingClientRect();
const y = e.clientY - rect.top; //y position within the element.
@@ -293,12 +297,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => {
const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
- activeItem.presX = NumCast(targetDoc.x);
- activeItem.presY = NumCast(targetDoc.y);
- activeItem.presRotation = NumCast(targetDoc.rotation);
- activeItem.presWidth = NumCast(targetDoc.width);
- activeItem.presHeight = NumCast(targetDoc.height);
- activeItem.presPinLayout = true;
+ activeItem.config_x = NumCast(targetDoc.x);
+ activeItem.config_y = NumCast(targetDoc.y);
+ activeItem.config_rotation = NumCast(targetDoc.rotation);
+ activeItem.config_width = NumCast(targetDoc.width);
+ activeItem.config_height = NumCast(targetDoc.height);
+ activeItem.config_pinLayout = true;
};
/**
* Method called for updating the view of the currently selected document
@@ -432,7 +436,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div
className="slideButton"
onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedContainerLayout(targetDoc, activeItem), true)}
- style={{ opacity: activeItem.presPinLayout ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
+ style={{ opacity: activeItem.config_pinLayout ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
L
</div>
</Tooltip>
@@ -442,7 +446,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div
className="slideButton"
onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))}
- style={{ opacity: activeItem.presPinData || activeItem.presPinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
+ style={{ opacity: activeItem.config_pinData || activeItem.config_pinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
C
</div>
</Tooltip>
@@ -456,19 +460,29 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
if (this.indexInPres !== 0) {
items.push(
- <Tooltip key="arrow" title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}>
+ <Tooltip
+ key="arrow"
+ title={
+ <div className="dash-tooltip">
+ {!activeItem.presentation_groupWithUp
+ ? 'Not grouped with previous slide (click to group)'
+ : activeItem.presentation_groupWithUp === 1
+ ? 'Run simultaneously with previous slide (click again to run after)'
+ : 'Run after previous slide (click to ungroup from previous)'}
+ </div>
+ }>
<div
className="slideButton"
- onClick={() => (activeItem.groupWithUp = (NumCast(activeItem.groupWithUp) + 1) % 3)}
+ onClick={() => (activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3)}
style={{
zIndex: 1000 - this.indexInPres,
fontWeight: 700,
- backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
- outline: NumCast(activeItem.groupWithUp) > 1 ? 'solid black 1px' : undefined,
- height: activeItem.groupWithUp ? 53 : 18,
- transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined,
+ backgroundColor: activeItem.presentation_groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
+ outline: NumCast(activeItem.presentation_groupWithUp) > 1 ? 'solid black 1px' : undefined,
+ height: activeItem.presentation_groupWithUp ? 53 : 18,
+ transform: activeItem.presentation_groupWithUp ? 'translate(0, -17px)' : undefined,
}}>
- <div style={{ transform: activeItem.groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
+ <div style={{ transform: activeItem.presentation_groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
<FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
</div>
</div>
@@ -476,14 +490,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
items.push(
- <Tooltip key="eye" title={<div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}</div>}>
+ <Tooltip key="eye" title={<div className="dash-tooltip">{this.rootDoc.presentation_expandInlineButton ? 'Minimize' : 'Expand'}</div>}>
<div
className="slideButton"
onClick={e => {
e.stopPropagation();
this.presExpandDocumentClick();
}}>
- <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
+ <FontAwesomeIcon icon={this.rootDoc.presentation_expandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
</div>
</Tooltip>
);
@@ -561,13 +575,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{
display: 'inline-flex',
pointerEvents: isSelected ? undefined : 'none',
- width: `calc(100% ${this.rootDoc.presExpandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
+ width: `calc(100% ${this.rootDoc.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
cursor: isSelected ? 'text' : 'grab',
}}>
<div
className="presItem-number"
title="select without navigation"
- style={{ pointerEvents: this.presBoxView?.isContentActive() ? 'all' : undefined }}
onPointerDown={e => {
e.stopPropagation();
if (this._itemRef.current && this._dragRef.current) {
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 5480600b0..8e53a87f6 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -15,6 +15,9 @@ import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
import { LightboxView } from '../LightboxView';
import { EditorView } from 'prosemirror-view';
import './AnchorMenu.scss';
+import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -42,7 +45,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
];
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
- @observable private _showLinkPopup: boolean = false;
@observable public Status: 'marquee' | 'annotation' | '' = '';
@@ -131,7 +133,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
() => this._opacity,
opacity => {
if (!opacity) {
- this._showLinkPopup = false;
this.setGPTPopupVis(false);
this.setGPTPopupText('');
}
@@ -141,7 +142,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this._disposer = reaction(
() => SelectionManager.Views().slice(),
selected => {
- this._showLinkPopup = false;
this.setGPTPopupVis(false);
this.setGPTPopupText('');
AnchorMenu.Instance.fadeOut(true);
@@ -253,49 +253,22 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
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 = (
- <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: 'rotate(-45deg)' }} />
- <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
- </div>
- </button>
- );
-
- const dropdownContent = (
- <div className="dropdown">
- <p>Change highlighter color:</p>
- <div className="color-wrapper">
- {this._palette.map(color => {
- if (color) {
- return this.highlightColor === color ? (
- <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button>
- ) : (
- <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button>
- );
- }
- })}
- </div>
- </div>
- );
return (
- <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} />
- </div>
- </Tooltip>
+ <Group>
+ <IconButton
+ icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
+ tooltip={'Click to Highlight'}
+ onClick={this.highlightClicked}
+ colorPicker={this.highlightColor}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ <ColorPicker selectedColor={this.highlightColor} setFinalColor={this.changeHighlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} />
+ </Group>
);
}
- @action changeHighlightColor = (color: string, e: React.PointerEvent) => {
+ @action changeHighlightColor = (color: string) => {
const col: ColorState = {
hex: color,
hsl: { a: 0, h: 0, s: 0, l: 0, source: '' },
@@ -304,8 +277,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
oldHue: 0,
source: '',
};
- e.preventDefault();
- e.stopPropagation();
this.highlightColor = Utils.colorString(col);
};
@@ -317,7 +288,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
canSummarize = (): boolean => {
const docs = SelectionManager.Docs();
if (docs.length > 0) {
- return docs.some(doc => doc.type === 'pdf' || doc.type === 'web');
+ return docs.some(doc => doc.type === DocumentType.PDF || doc.type === DocumentType.WEB);
}
return false;
};
@@ -339,18 +310,20 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
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>
+ <IconButton
+ tooltip="Drag to Place Annotation" //
+ onPointerDown={this.pointerDown}
+ icon={<FontAwesomeIcon icon="comment-alt" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
- <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}>
- <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}>
- <FontAwesomeIcon icon="comment-dots" size="lg" />
- </button>
- </Tooltip>
+ <IconButton
+ tooltip="Summarize with AI" //
+ onPointerDown={this.gptSummarize}
+ icon={<FontAwesomeIcon icon="comment-dots" size="lg" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
)}
<GPTPopup
key="gptpopup"
@@ -364,59 +337,74 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
mode={this.GPTMode}
/>
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
- <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">Click to Record Annotation</div>}>
- <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}>
- <FontAwesomeIcon icon="microphone" size="lg" />
- </button>
- </Tooltip>
+ <IconButton
+ tooltip="Click to Record Annotation" //
+ onPointerDown={this.audioDown}
+ icon={<FontAwesomeIcon icon="microphone" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
)}
{this.canEdit() && (
- <Tooltip key="gpttextedit" title={<div className="dash-tooltip">AI edit suggestions</div>}>
- <button className="antimodeMenu-button annotate" onPointerDown={this.gptEdit} style={{ cursor: 'grab' }}>
- <FontAwesomeIcon icon="pencil-alt" size="lg" />
- </button>
- </Tooltip>
+ <IconButton
+ tooltip="AI edit suggestions" //
+ onPointerDown={this.gptEdit}
+ icon={<FontAwesomeIcon icon="pencil-alt" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
)}
- <Tooltip key="link" title={<div className="dash-tooltip">Find document to link to selected text</div>}>
- <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup}>
- <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
- <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} linkCreateAnchor={this.onMakeAnchor} />,
+ <Popup
+ tooltip="Find document to link to selected text" //
+ type={Type.PRIM}
+ icon={<FontAwesomeIcon icon={'search'} />}
+ popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
{AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
- <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>
+ <IconButton
+ tooltip="Click/Drag to create cropped image" //
+ onPointerDown={this.cropDown}
+ icon={<FontAwesomeIcon icon="image" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
)}
</>
) : (
<>
- <Tooltip key="trash" title={<div className="dash-tooltip">Remove Link Anchor</div>}>
- <button className="antimodeMenu-button" style={{ display: this.Delete === returnFalse ? 'none' : undefined }} 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" style={{ display: this.PinToPres === returnFalse ? 'none' : undefined }} onPointerDown={this.PinToPres}>
- <FontAwesomeIcon icon="map-pin" size="lg" />
- </button>
- </Tooltip>
- <Tooltip key="trail" title={<div className="dash-tooltip">Show Linked Trail</div>}>
- <button className="antimodeMenu-button" style={{ display: this.ShowTargetTrail === returnFalse ? 'none' : undefined }} onPointerDown={this.ShowTargetTrail}>
- <FontAwesomeIcon icon="taxi" size="lg" />
- </button>
- </Tooltip>
- <Tooltip key="toggle" title={<div className="dash-tooltip">make target visibility toggle on click</div>}>
- <button
- className="antimodeMenu-button"
- style={{ display: this.IsTargetToggler === returnFalse ? 'none' : undefined, color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }}
- onPointerDown={this.MakeTargetToggle}>
- <FontAwesomeIcon icon="thumbtack" size="lg" />
- </button>
- </Tooltip>
+ {this.Delete !== returnFalse && (
+ <IconButton
+ tooltip="Remove Link Anchor" //
+ onPointerDown={this.Delete}
+ icon={<FontAwesomeIcon icon="trash-alt" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )}
+ {this.PinToPres !== returnFalse && (
+ <IconButton
+ tooltip="Pin to Presentation" //
+ onPointerDown={this.PinToPres}
+ icon={<FontAwesomeIcon icon="map-pin" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )}
+ {this.ShowTargetTrail !== returnFalse && (
+ <IconButton
+ tooltip="Show Linked Trail" //
+ onPointerDown={this.ShowTargetTrail}
+ icon={<FontAwesomeIcon icon="taxi" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )}
+ {this.IsTargetToggler !== returnFalse && (
+ <Toggle
+ tooltip={'Make target visibility toggle on click'}
+ type={Type.PRIM}
+ toggleType={ToggleType.BUTTON}
+ toggleStatus={this.IsTargetToggler()}
+ onClick={this.MakeTargetToggle}
+ icon={<FontAwesomeIcon icon="thumbtack" />}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ )}
</>
);
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index db6b1f011..caa72c9dc 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -24,7 +24,7 @@ export class Annotation extends React.Component<IAnnotationProps> {
render() {
return (
<div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}>
- {DocListCast(this.props.anno.textInlineAnnotations).map(a => (
+ {DocListCast(this.props.anno.text_inlineAnnotations).map(a => (
<RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
))}
</div>
@@ -61,7 +61,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
@undoBatch
showTargetTrail = (anchor: Doc) => {
- const trail = DocCast(anchor.presTrail);
+ const trail = DocCast(anchor.presentationTrail);
if (trail) {
Doc.ActivePresentation = trail;
this.props.addDocTab(trail, OpenWhere.replaceRight);
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 470aa3eb1..cfe07f6cb 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -26,15 +26,9 @@
.textLayer {
opacity: unset;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
-
- // span {
- // padding-right: 5px;
- // padding-bottom: 4px;
- // }
}
-
.textLayer ::selection {
- background: #accef7;
+ background: #accef76a;
}
// should match the backgroundColor in createAnnotation()
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index dd202418b..395fbd1ca 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -26,6 +26,7 @@ import { Annotation } from './Annotation';
import './PDFViewer.scss';
import React = require('react');
import { GPTPopup } from './GPTPopup/GPTPopup';
+import { InkingStroke } from '../InkingStroke';
const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
const pdfjsLib = require('pdfjs-dist');
const _global = (window /* browser */ || global) /* node */ as any;
@@ -67,7 +68,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _styleRule: any; // stylesheet rule for making hyperlinks clickable
private _retries = 0; // number of times tried to create the PDF viewer
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
- private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void);
+ private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void);
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -94,7 +95,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges());
}
@computed get inlineTextAnnotations() {
- return this.allAnnotations.filter(a => a.textInlineAnnotations);
+ return this.allAnnotations.filter(a => a.text_inlineAnnotations);
}
componentDidMount = async () => {
@@ -194,7 +195,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
return focusSpeed;
};
crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
- brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
+ brushView = (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => this._setBrushViewer?.(view, transTime);
@action
setupPdfJsViewer = async () => {
@@ -469,7 +470,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
};
setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
- setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
+ setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => (this._setBrushViewer = func);
@action
onZoomWheel = (e: React.WheelEvent) => {
@@ -483,16 +484,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
};
- pointerEvents = () => (this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
+ pointerEvents = () =>
+ this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
+ ? 'all' //
+ : 'none';
@computed get annotationLayer() {
+ const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden);
return (
<div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
- {this.inlineTextAnnotations
- .sort((a, b) => NumCast(a.y) - NumCast(b.y))
- .filter(anno => !anno.hidden)
- .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`} />
- ))}
+ {inlineAnnos.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>
);
}
@@ -514,11 +516,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
- opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
+ opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length && this.props.isContentActive() ? [] : [Utils.IsOpaqueFilter()])];
childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
- if (this.inlineTextAnnotations.includes(doc)) return 'none';
- return 'all';
+ if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
+ const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString;
+ return isInk ? 'visiblePainted' : 'all';
}
return this.props.styleProvider?.(doc, props, property);
};
@@ -536,8 +539,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
NativeWidth={returnZero}
NativeHeight={returnZero}
setContentView={emptyFunction} // override setContentView to do nothing
- pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
- childPointerEvents="all" // but freeform children need to get events to allow text editing, etc
+ pointerEvents={this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
+ childPointerEvents={this.props.isContentActive() !== false ? 'all' : 'none'} // but freeform children need to get events to allow text editing, etc
renderDepth={this.props.renderDepth + 1}
isAnnotationOverlay={true}
fieldKey={this.props.fieldKey + '_annotations'}
@@ -549,7 +552,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
ScreenToLocalTransform={this.overlayTransform}
isAnyChildContentActive={returnFalse}
isAnnotationOverlayScrollable={true}
- dropAction="embed"
childFilters={childFilters}
select={emptyFunction}
bringToFront={emptyFunction}
@@ -558,14 +560,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
</div>
);
@computed get overlayTransparentAnnotations() {
- return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined);
+ const transparentChildren = DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.transparentFilter(), []);
+ return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length && this.props.isContentActive() ? 'none' : undefined);
}
@computed get overlayOpaqueAnnotations() {
return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
}
@computed get overlayLayer() {
return (
- <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
+ <div style={{ pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
{this.overlayTransparentAnnotations}
{this.overlayOpaqueAnnotations}
</div>
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index e8865b918..a439aea3e 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -13,12 +13,16 @@
.searchBox-bar {
width: 100%;
- height: 35px;
+ height: fit-content;
display: flex;
justify-content: center;
align-items: center;
background-color: none;
padding: 5px;
+ top: 0px;
+ position: sticky;
+ overflow-y: scroll;
+ border-bottom: $standard-border;
.searchBox-type {
display: block;
@@ -42,34 +46,66 @@
}
}
- .searchBox-results-container {
+ .section-header {
+
+ .section-title {
+ font-size: $body-text;
+ font-weight: 600;
+ }
+
+ .section-subtitle {
+ display: flex;
+ color: $light-gray;
+ }
+
+ padding: 5px 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ background: $medium-blue;
+ color: white;
+ }
+
+ .searchBox-recommendations-container {
display: flex;
flex-direction: column;
width: 100%;
- height: 100%;
+ height: fit-content;
justify-content: "center";
-
- .searchBox-results-count {
+
+ .searchBox-recommendations-view {
+ margin-top: 10px;
display: flex;
- color: gray;
- margin-left: 5px;
+ width: 100%;
+ height: fit-content;
+ flex-direction: column;
+ gap: 10px;
+ padding: 0px 10px;
+
+
}
+ }
+
+ .searchBox-results-container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: fit-content;
+ justify-content: "center";
- .searchBox-results-scroll-view {
- margin-top: 10px;
+ .searchBox-results-view {
display: inline-block;
width: 100%;
- height: calc(100% - 55px);
- overflow-y: scroll;
+ height: fit-content;
.searchBox-results-scroll-view-result {
display: inline-block;
vertical-align: middle;
width: 100%;
- height: 50px;
+ height: fit-content;
cursor: pointer;
font-size: 15px;
- padding: 11px;
+ padding: 10px;
&.searchBox-results-scroll-view-result-selected {
background: #999;
@@ -81,6 +117,8 @@
width: calc(100% - 45px);
text-align: left;
overflow: hidden;
+ max-height: 2.4em;
+ line-height: 1.2em;
text-overflow: ellipsis;
}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index d13c09443..1ceea697a 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -15,6 +15,10 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
import { ViewBoxBaseComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import './SearchBox.scss';
+import { fetchRecommendations } from '../newlightbox/utils';
+import { IRecommendation, Recommendation } from '../newlightbox/components';
+import { Colors } from '../global/globalEnums';
+import { SettingsManager } from '../../util/SettingsManager';
const DAMPENING_FACTOR = 0.9;
const MAX_ITERATIONS = 25;
@@ -43,6 +47,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
@observable _searchString = '';
@observable _docTypeString = 'all';
@observable _results: Map<Doc, string[]> = new Map<Doc, string[]>();
+ @observable _recommendations: IRecommendation[] = [];
@observable _pageRanks: Map<Doc, number> = new Map<Doc, number>();
@observable _linkedDocsOut: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>();
@observable _linkedDocsIn: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>();
@@ -214,7 +219,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
}
@action
static staticSearchCollection(rootDoc: Opt<Doc>, query: string) {
- const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+ const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.SEARCH, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = [
'x',
'y',
@@ -222,7 +227,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
'width',
'layout_autoHeight',
'acl-Override',
- 'acl-Public',
+ 'acl-Guest',
'embedContainer',
'zIndex',
'height',
@@ -394,6 +399,37 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
if (query) {
this.searchCollection(query);
+ const response = await fetchRecommendations('', query, [], true);
+ const recs = response.recommendations;
+ const recommendations: IRecommendation[] = [];
+ for (const key in recs) {
+ const title = recs[key].title;
+ const url = recs[key].url;
+ const type = recs[key].type;
+ const text = recs[key].text;
+ const transcript = recs[key].transcript;
+ const previewUrl = recs[key].previewUrl;
+ const embedding = recs[key].embedding;
+ const distance = recs[key].distance;
+ const source = recs[key].source;
+ const related_concepts = recs[key].related_concepts;
+ const docId = recs[key].doc_id;
+ recommendations.push({
+ title: title,
+ data: url,
+ type: type,
+ text: text,
+ transcript: transcript,
+ previewUrl: previewUrl,
+ embedding: embedding,
+ distance: Math.round(distance * 100) / 100,
+ source: source,
+ related_concepts: related_concepts,
+ docId: docId,
+ });
+ }
+ const setRecommendations = action(() => (this._recommendations = recommendations));
+ setRecommendations();
}
};
@@ -403,6 +439,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
*/
resetSearch = action(() => {
this._results.forEach((_, doc) => {
+ DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
Doc.UnBrushDoc(doc);
Doc.UnHighlightDoc(doc);
Doc.ClearSearchMatches();
@@ -425,7 +462,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
*/
@computed
public get selectOptions() {
- const selectValues = ['all', 'rtf', 'image', 'pdf', 'web', 'video', 'audio', 'collection'];
+ const selectValues = ['all', DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.WEB, DocumentType.VID, DocumentType.AUDIO, DocumentType.COL];
return selectValues.map(value => (
<option key={value} value={value}>
@@ -481,15 +518,19 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
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 className="searchBox-result-keys" style={{ color: SettingsManager.Instance.userVariantColor }}>
+ {result[1].join(', ')}
+ </div>
</div>
</Tooltip>
);
}
});
+ const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => <Recommendation {...props} />);
+
return (
- <div style={{ pointerEvents: 'all' }} className="searchBox-container">
+ <div className="searchBox-container" style={{ pointerEvents: 'all', color: SettingsManager.Instance.userColor, background: SettingsManager.Instance.userBackgroundColor }}>
<div className="searchBox-bar">
{isLinkSearch ? null : (
<select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
@@ -512,10 +553,24 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
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>
+ {resultsJSX.length > 0 && (
+ <div className="searchBox-results-container">
+ <div className="section-header" style={{ background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE) }}>
+ <div className="section-title">Results</div>
+ <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ </div>
+ <div className="searchBox-results-view">{resultsJSX}</div>
+ </div>
+ )}
+ {recommendationsJSX.length > 0 && (
+ <div className="searchBox-recommendations-container">
+ <div className="section-header" style={{ background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE) }}>
+ <div className="section-title">Recommendations</div>
+ <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ </div>
+ <div className="searchBox-recommendations-view">{recommendationsJSX}</div>
+ </div>
+ )}
</div>
);
}
diff --git a/src/client/views/selectedDoc/SelectedDocView.scss b/src/client/views/selectedDoc/SelectedDocView.scss
new file mode 100644
index 000000000..156dfc37b
--- /dev/null
+++ b/src/client/views/selectedDoc/SelectedDocView.scss
@@ -0,0 +1,3 @@
+.selectedDocView-container {
+
+} \ No newline at end of file
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
new file mode 100644
index 000000000..955a4a174
--- /dev/null
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -0,0 +1,47 @@
+import React = require('react');
+import { Doc } from "../../../fields/Doc";
+import { observer } from "mobx-react";
+import { computed } from "mobx";
+import { StrCast } from "../../../fields/Types";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Colors, ListBox } from 'browndash-components';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DocFocusOptions } from '../nodes/DocumentView';
+
+export interface SelectedDocViewProps {
+ selectedDocs: Doc[];
+}
+
+@observer
+export class SelectedDocView extends React.Component<SelectedDocViewProps> {
+
+ @computed get selectedDocs() {
+ return this.props.selectedDocs;
+ }
+
+
+ render() {
+ return <div className={`selectedDocView-container`}>
+ <ListBox
+ items={this.selectedDocs.map((doc) => {
+ const icon = Doc.toIcon(doc);
+ const iconEle = <FontAwesomeIcon size={'1x'} icon={icon} />;
+ const text = StrCast(doc.title)
+ const finished = () => {
+
+ };
+ const options: DocFocusOptions = {
+ playAudio: false,
+ };
+ return {
+ text: text,
+ val: StrCast(doc._id),
+ icon: iconEle,
+ onClick: () => {DocumentManager.Instance.showDocument(doc, options, finished);}
+ }
+ })}
+ color={StrCast(Doc.UserDoc().userColor)}
+ />
+ </div>
+ }
+} \ No newline at end of file
diff --git a/src/client/views/selectedDoc/index.ts b/src/client/views/selectedDoc/index.ts
new file mode 100644
index 000000000..1f1db91f6
--- /dev/null
+++ b/src/client/views/selectedDoc/index.ts
@@ -0,0 +1 @@
+export * from './SelectedDocView' \ No newline at end of file
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index a1131b92e..2237d5ac1 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -1,243 +1,240 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables';
.topbar-container {
- 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;
- border-bottom: $standard-border;
- padding: 0px 10px;
- cursor: default;
- display: flex;
- justify-content: center;
+ 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;
+ border-bottom: $standard-border;
+ padding: 0px 10px;
+ cursor: default;
+ display: flex;
+ justify-content: center;
+ width: 100%;
+
+ .topbar-inner-container {
+ display: flex;
+ flex-direction: row;
+ position: relative;
+ display: grid;
+ grid-auto-columns: 33.3% 33.3% 33.3%;
width: 100%;
+ align-items: center;
- .topbar-inner-container {
- display: flex;
- flex-direction: row;
- position: relative;
- display: grid;
- grid-auto-columns: 33.3% 33.3% 33.3%;
- width: 100%;
- align-items: center;
-
- // &:first-child {
- // height: 20px;
- // }
- }
+ // &:first-child {
+ // height: 20px;
+ // }
+ }
- .topbar-button-text {
- color: $white;
- padding: 10px;
- size: 15;
+ .topbar-button-text {
+ color: $white;
+ padding: 10px;
+ size: 15;
- &:hover {
- font-weight: 500;
- }
- }
+ &:hover {
+ font-weight: 500;
+ }
+ }
- .topbar-button-icon {
- cursor: pointer;
- width: fit-content;
+ .topbar-button-icon {
+ cursor: pointer;
+ width: fit-content;
+ display: flex;
+ justify-content: center;
+ gap: 4px;
+ align-items: center;
+ justify-self: center;
+ align-self: center;
+ padding: 5px;
+ transition: linear 0.2s;
+ color: $white;
+
+ &:hover {
+ background-color: darken($color: $light-gray, $amount: 20);
+ font-weight: 500;
+ }
+ }
+
+ .topbar-title {
+ color: $white;
+ font-size: 17;
+ font-weight: 500;
+ }
+
+ .topbar-center {
+ grid-column: 2;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ gap: 5px;
+
+ .topbar-dashboard-header {
+ font-weight: 600;
+ }
+ }
+
+ .topbar-right {
+ grid-column: 3;
+ position: relative;
+ display: flex;
+ justify-content: flex-end;
+ gap: 5px;
+ margin-right: 5px;
+ }
+
+ .topbar-left {
+ grid-column: 1;
+ color: black;
+ font-family: 'Roboto';
+ position: relative;
+ display: flex;
+ width: fit-content;
+ gap: 5px;
+
+ .logo-container {
+ font-size: 15;
display: flex;
+ flex-direction: row;
justify-content: center;
- gap: 4px;
align-items: center;
- justify-self: center;
- align-self: center;
- padding: 5px;
- transition: linear 0.2s;
- color: $white;
-
- &:hover {
- background-color: darken($color: $light-gray, $amount: 20);
- font-weight: 500;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ .logo {
+ background-color: transparent;
+ width: 25px;
+ height: 25px;
+ margin-right: 5px;
}
- }
+ }
- .topbar-title {
- color: $white;
- font-size: 17;
- font-weight: 500;
- }
-
- .topbar-center {
- grid-column: 2;
- display: inline-flex;
- justify-content: center;
- align-items: center;
- gap: 5px;
-
- .topbar-dashboard-header {
- font-weight: 600;
- }
- }
+ .topBar-icon:hover {
+ background-color: $close-red;
+ }
-
- .topbar-right {
- grid-column: 3;
- position: relative;
- display: flex;
- justify-content: flex-end;
- gap: 5px;
- margin-right: 5px;
- }
-
- .topbar-left {
- grid-column: 1;
- color: black;
+ .topbar-lozenge-user,
+ .topbar-lozenge {
+ height: 23;
+ font-size: 12;
+ color: white;
font-family: 'Roboto';
- position: relative;
+ font-weight: 400;
+ padding: 4px;
+ align-self: center;
+ margin-left: 7px;
display: flex;
- width: fit-content;
- gap: 5px;
-
- .logo-container {
- font-size: 15;
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
-
- .logo {
- background-color: transparent;
- width: 25px;
- height: 25px;
- margin-right: 5px;
-
- }
- }
+ align-items: center;
- .topBar-icon:hover {
- background-color: $close-red;
- }
+ .topbar-dashSelect {
+ border: none;
+ background-color: transparent;
+ color: black;
+ font-family: 'Roboto';
+ font-size: 17;
+ font-weight: 500;
- .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;
- }
- }
+ &:hover {
+ cursor: pointer;
+ }
}
+ }
+
+ .topbar-logoff {
+ border-radius: 3px;
+ background: olivedrab;
+ color: white;
+ display: none;
+ margin-left: 5px;
+ padding: 1px 2px 1px 2px;
+ cursor: pointer;
+ }
- .topbar-logoff {
- border-radius: 3px;
- background: olivedrab;
- color: white;
- display: none;
- margin-left: 5px;
- padding: 1px 2px 1px 2px;
- cursor: pointer;
- }
+ .topbar-logoff {
+ background: red;
+ }
+ .topbar-lozenge-user:hover {
.topbar-logoff {
- background: red;
+ display: inline-block;
}
-
- .topbar-lozenge-user:hover {
- .topbar-logoff {
- display: inline-block;
- }
- }
- }
-
- .topbar-barChild {
-
- &.topbar-collection {
- 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;
- }
+ }
+ }
+
+ .topbar-barChild {
+ &.topbar-collection {
+ 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;
}
+ }
- &.topbar-filter {
- align-self: stretch;
+ &.topbar-filter {
+ align-self: stretch;
- button {
- transform: none;
+ button {
+ transform: none;
- &:hover {
- transform: none;
- }
- }
+ &:hover {
+ transform: none;
+ }
}
+ }
- &.topbar-submit {
- margin-left: 2px;
- margin-right: 2px
- }
+ &.topbar-submit {
+ margin-left: 2px;
+ margin-right: 2px;
+ }
- &.topbar-close {
- color: $white;
- max-height: $topbar-height;
- }
- }
+ &.topbar-close {
+ 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;
- }
-} \ No newline at end of file
+ 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;
+ }
+}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 71daad1a9..c194ede32 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,25 +1,27 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, IconButton, Size } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { Button, IconButton, Size, Type, isDark } from 'browndash-components';
+import { action, computed, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaBug, FaCamera, FaStamp } from 'react-icons/fa';
-import { Doc } from '../../../fields/Doc';
-import { AclAdmin } from '../../../fields/DocSymbols';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
import { StrCast } from '../../../fields/Types';
import { GetEffectiveAcl } from '../../../fields/util';
import { DocumentManager } from '../../util/DocumentManager';
-import { ReportManager } from '../../util/ReportManager';
+import { PingManager } from '../../util/PingManager';
+import { ReportManager } from '../../util/reportManager/ReportManager';
import { ServerStats } from '../../util/ServerStats';
import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
import { UndoManager } from '../../util/UndoManager';
-import { CollectionDockingView } from '../collections/CollectionDockingView';
import { ContextMenu } from '../ContextMenu';
import { DashboardView } from '../DashboardView';
-import { Colors } from '../global/globalEnums';
import { MainView } from '../MainView';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { Colors } from '../global/globalEnums';
import './TopBar.scss';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
/**
* ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
@@ -34,8 +36,22 @@ export class TopBar extends React.Component {
});
};
- @observable textColor: string = Colors.LIGHT_GRAY;
- @observable backgroundColor: string = Colors.DARK_GRAY;
+ @computed get color() {
+ return StrCast(Doc.UserDoc().userColor, Colors.LIGHT_GRAY);
+ }
+ @computed get variantColor() {
+ return StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE);
+ }
+ @computed get backgroundColor() {
+ return PingManager.Instance.IsBeating ? SettingsManager.Instance.userBackgroundColor : Colors.MEDIUM_GRAY;
+ }
+
+ @observable happyHeart: boolean = PingManager.Instance.IsBeating;
+ setHappyHeart = action((status: boolean) => (this.happyHeart = status));
+ dispose = reaction(
+ () => PingManager.Instance.IsBeating,
+ isBeating => this.setHappyHeart(isBeating)
+ );
/**
* Returns the left hand side of the topbar.
@@ -48,16 +64,20 @@ export class TopBar extends React.Component {
return (
<div className="topbar-left">
{Doc.ActiveDashboard ? (
- <IconButton onClick={this.navigateToHome} icon={<FontAwesomeIcon icon="home" />} color={this.textColor} />
+ <IconButton
+ onClick={this.navigateToHome}
+ icon={<FontAwesomeIcon icon={DocListCast(Doc.MySharedDocs.data_dashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'portrait' : 'home'} />}
+ color={this.color}
+ />
) : (
<div className="logo-container">
<img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo"></img>
- <span style={{ color: Colors.LIGHT_GRAY, fontWeight: 200 }}>brown</span>
- <span style={{ color: Colors.LIGHT_BLUE, fontWeight: 500 }}>dash</span>
+ <span style={{ color: isDark(this.backgroundColor) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY, fontWeight: 200 }}>brown</span>
+ <span style={{ color: isDark(this.backgroundColor) ? Colors.LIGHT_BLUE : Colors.MEDIUM_BLUE, fontWeight: 500 }}>dash</span>
</div>
)}
{Doc.ActiveDashboard && (
- <Button text="Explore" tooltip="Browsing mode for directly navigating to documents" size={Size.SMALL} color={this.textColor} onClick={action(() => (MainView.Instance._exploreMode = !MainView.Instance._exploreMode))} />
+ <Button text="Explore" tooltip="Browsing mode for directly navigating to documents" size={Size.SMALL} color={this.color} onClick={action(() => (MainView.Instance._exploreMode = !MainView.Instance._exploreMode))} />
)}
</div>
);
@@ -84,36 +104,21 @@ export class TopBar extends React.Component {
<div className="topbar-center">
<Button
text={StrCast(Doc.ActiveDashboard.title)}
- tooltip="Browsing mode for directly navigating to documents"
+ tooltip="Open Dashboards"
size={Size.SMALL}
- color={'white'}
+ color={this.color}
+ style={{ fontWeight: 700, fontSize: '1rem' }}
onClick={(e: React.MouseEvent) => {
const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.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);
}}
/>
- <Button
- text={GetEffectiveAcl(Doc.GetProto(Doc.ActiveDashboard)) === AclAdmin ? 'Share' : 'View Original'}
- onClick={() => {
- SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
- }}
- size={Size.SMALL}
- />
{!Doc.noviceMode && (
<IconButton
tooltip="Work on a copy of the dashboard layout"
size={Size.SMALL}
- color={this.textColor}
+ color={this.color}
onClick={async () => {
const batch = UndoManager.StartBatch('snapshot');
await DashboardView.snapshotDashboard();
@@ -132,13 +137,31 @@ export class TopBar extends React.Component {
* and allows the user to access their account settings etc.
*/
@computed get topbarRight() {
+ const upToDate = DashVersion === CurrentUserUtils.ServerVersion;
return (
<div className="topbar-right">
- <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ServerStats.Instance.open} icon={<FaStamp />} />
- <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ReportManager.Instance.open} icon={<FaBug />} />
- <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
- <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
- {/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.textColor} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */}
+ {Doc.ActiveDashboard ? (
+ <Button
+ text={GetEffectiveAcl(Doc.ActiveDashboard) === AclAdmin ? 'Share' : 'View Original'}
+ type={Type.TERT}
+ color={this.variantColor}
+ onClick={() => {
+ SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
+ }}
+ />
+ ) : null}
+ <IconButton tooltip={'Issue Reporter ⌘I'} size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
+ <IconButton tooltip={'Documentation ⌘D'} size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+ <IconButton tooltip={'Settings ⌘⇧S'} size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
+ <IconButton
+ size={Size.SMALL}
+ onClick={ServerStats.Instance.open}
+ type={Type.TERT}
+ tooltip={'Server is ' + (PingManager.Instance.IsBeating ? '' : 'NOT ') + 'running ' + (upToDate ? DashVersion : 'out of date version:' + DashVersion)}
+ color={this.happyHeart ? (upToDate ? Colors.LIGHT_BLUE : Colors.YELLOW) : Colors.ERROR_RED}
+ icon={<FontAwesomeIcon icon={this.happyHeart ? 'heart' : 'heart-broken'} />}
+ />
+ {/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.color} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */}
</div>
);
}
@@ -146,13 +169,15 @@ export class TopBar extends React.Component {
render() {
return (
//TODO:glr Add support for light / dark mode
- <div style={{ pointerEvents: 'all' }} className="topbar-container">
- <div
- className="topbar-inner-container"
- style={{
- color: this.textColor,
- background: this.backgroundColor,
- }}>
+ <div
+ style={{
+ pointerEvents: 'all',
+ color: this.color,
+ background: this.backgroundColor,
+ // borderColor: this.color
+ }}
+ className="topbar-container">
+ <div className="topbar-inner-container">
{this.topbarLeft}
{this.topbarCenter}
{this.topbarRight}
diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts
index a8a2859d2..46f5a8e1c 100644
--- a/src/fields/CursorField.ts
+++ b/src/fields/CursorField.ts
@@ -1,44 +1,43 @@
-import { ObjectField } from "./ObjectField";
-import { observable } from "mobx";
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, createSimpleSchema, object, date } from "serializr";
-import { OnUpdate, ToScriptString, ToString, Copy } from "./FieldSymbols";
+import { ObjectField } from './ObjectField';
+import { observable } from 'mobx';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, createSimpleSchema, object, date } from 'serializr';
+import { FieldChanged, ToScriptString, ToString, Copy } from './FieldSymbols';
export type CursorPosition = {
- x: number,
- y: number
+ x: number;
+ y: number;
};
export type CursorMetadata = {
- id: string,
- identifier: string,
- timestamp: number
+ id: string;
+ identifier: string;
+ timestamp: number;
};
export type CursorData = {
- metadata: CursorMetadata,
- position: CursorPosition
+ metadata: CursorMetadata;
+ position: CursorPosition;
};
const PositionSchema = createSimpleSchema({
x: true,
- y: true
+ y: true,
});
const MetadataSchema = createSimpleSchema({
id: true,
identifier: true,
- timestamp: true
+ timestamp: true,
});
const CursorSchema = createSimpleSchema({
metadata: object(MetadataSchema),
- position: object(PositionSchema)
+ position: object(PositionSchema),
});
-@Deserializable("cursor")
+@Deserializable('cursor')
export default class CursorField extends ObjectField {
-
@serializable(object(CursorSchema))
readonly data: CursorData;
@@ -50,7 +49,7 @@ export default class CursorField extends ObjectField {
setPosition(position: CursorPosition) {
this.data.position = position;
this.data.metadata.timestamp = Date.now();
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
[Copy]() {
@@ -58,9 +57,9 @@ export default class CursorField extends ObjectField {
}
[ToScriptString]() {
- return "invalid";
+ return 'invalid';
}
[ToString]() {
- return "invalid";
+ return 'invalid';
}
-} \ No newline at end of file
+}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 92ae64567..ceacb8a08 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -20,8 +20,6 @@ import {
AclEdit,
AclPrivate,
AclReadonly,
- AclSelfEdit,
- AclUnset,
Animation,
CachedUpdates,
DirectLinks,
@@ -38,11 +36,10 @@ import {
Initializing,
Self,
SelfProxy,
- Update,
UpdatingFromServer,
Width,
} from './DocSymbols';
-import { Copy, HandleUpdate, Id, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
import { InkField, InkTool } from './InkField';
import { List, ListFieldName } from './List';
import { ObjectField } from './ObjectField';
@@ -53,7 +50,7 @@ import { listSpec } from './Schema';
import { ComputedField, ScriptField } from './ScriptField';
import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
-import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util';
+import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, containedFieldChangedHandler } from './util';
import JSZip = require('jszip');
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -126,21 +123,18 @@ export enum aclLevel {
unshared = 0,
viewable = 1,
augmentable = 2,
- selfEditable = 2.5,
editable = 3,
admin = 4,
}
// prettier-ignore
-export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermissions }> = new Map([
- [AclPrivate, { level: aclLevel.unshared, name: SharingPermissions.None }],
- [AclReadonly, { level: aclLevel.viewable, name: SharingPermissions.View }],
- [AclAugment, { level: aclLevel.augmentable, name: SharingPermissions.Augment}],
- [AclSelfEdit, { level: aclLevel.selfEditable, name: SharingPermissions.SelfEdit }],
- [AclEdit, { level: aclLevel.editable, name: SharingPermissions.Edit }],
- [AclAdmin, { level: aclLevel.admin, name: SharingPermissions.Admin }],
- [AclUnset, { level: aclLevel.unset, name: SharingPermissions.Unset }],
+export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermissions; image: string }> = new Map([
+ [AclPrivate, { level: aclLevel.unshared, name: SharingPermissions.None, image: 'â–²' }],
+ [AclReadonly, { level: aclLevel.viewable, name: SharingPermissions.View, image: '♦' }],
+ [AclAugment, { level: aclLevel.augmentable, name: SharingPermissions.Augment, image: '⬟' }],
+ [AclEdit, { level: aclLevel.editable, name: SharingPermissions.Edit, image: '⬢' }],
+ [AclAdmin, { level: aclLevel.admin, name: SharingPermissions.Admin, image: '⬢' }],
]);
-export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol }> = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0] }]));
+export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol; image: string }> = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }]));
// caches the document access permissions for the current user.
// this recursively updates all protos as well.
@@ -244,22 +238,6 @@ export class Doc extends RefField {
public static get MyFilesystem() {
return DocCast(Doc.UserDoc().myFilesystem);
}
- public static get MyFileOrphans() {
- return DocCast(Doc.UserDoc().myFileOrphans);
- }
- public static AddFileOrphan(doc: Doc) {
- if (
- doc &&
- Doc.MyFileOrphans instanceof Doc &&
- Doc.IsDataProto(doc) &&
- !Doc.IsSystem(doc) &&
- ![DocumentType.CONFIG, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(doc.type as any) &&
- !doc.isFolder &&
- !doc.annotationOn
- ) {
- Doc.AddDocToList(Doc.MyFileOrphans, undefined, doc);
- }
- }
public static get MyTools() {
return DocCast(Doc.UserDoc().myTools);
}
@@ -340,9 +318,10 @@ export class Doc extends RefField {
for (const key in value) {
const field = value[key];
field !== undefined && (this[FieldKeys][key] = true);
- if (!(field instanceof ObjectField)) continue;
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ if (field instanceof ObjectField) {
+ field[Parent] = this[Self];
+ field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field);
+ }
}
}
@@ -351,7 +330,7 @@ export class Doc extends RefField {
/// all of the raw acl's that have been set on this document. Use GetEffectiveAcl to determine the actual ACL of the doc for editing
@observable public [DocAcl]: { [key: string]: symbol } = {};
@observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
- @observable public [DirectLinks]: Set<Doc> = new Set();
+ @observable public [DirectLinks] = new ObservableSet<Doc>();
@observable public [Animation]: Opt<Doc>;
@observable public [Highlight]: boolean = false;
static __Anim(Doc: Doc) {
@@ -363,12 +342,13 @@ export class Doc extends RefField {
private [ForceServerWrite]: boolean = false;
public [Initializing]: boolean = false;
- private [Update] = (diff: any) => {
- (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff);
- };
-
private [Self] = this;
private [SelfProxy]: any;
+ public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
+ if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
+ DocServer.UpdateField(this[Id], serverOp);
+ }
+ };
public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
public [Width] = () => NumCast(this[SelfProxy]._width);
public [Height] = () => NumCast(this[SelfProxy]._height);
@@ -382,7 +362,8 @@ export class Doc extends RefField {
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);
+ const self = this[SelfProxy];
+ const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null);
if (templateLayoutDoc) {
let renderFieldKey: any;
const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
@@ -391,7 +372,7 @@ export class Doc extends RefField {
} else {
return Cast(layoutField, Doc, null);
}
- return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
+ return Cast(self[renderFieldKey + '_layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
}
return undefined;
}
@@ -480,6 +461,9 @@ export namespace Doc {
// });
// }
+ export function SetContainer(doc: Doc, container: Doc) {
+ doc.embedContainer = container;
+ }
export function RunCachedUpdate(doc: Doc, field: string) {
const update = doc[CachedUpdates][field];
if (update) {
@@ -573,7 +557,7 @@ export namespace Doc {
// compare whether documents or their protos match
export function AreProtosEqual(doc?: Doc, other?: Doc) {
- return doc && other && Doc.GetProto(doc) === Doc.GetProto(other);
+ return doc && other && (doc === other || Doc.GetProto(doc) === Doc.GetProto(other));
}
// Gets the data document for the document. Note: this is mis-named -- it does not specifically
@@ -691,7 +675,7 @@ export namespace Doc {
Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
}
embedding.createdFrom = doc;
- embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = NumCast(Doc.GetProto(doc).proto_embeddingId) + 1;
+ embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1;
embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`);
embedding.author = Doc.CurrentUserEmail;
@@ -701,7 +685,8 @@ export namespace Doc {
}
export function BestEmbedding(doc: Doc) {
- const bestEmbedding = Doc.GetProto(doc) ? DocListCast(doc.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail) : doc;
+ const bestEmbedding = Doc.GetProto(doc) ? [doc, ...DocListCast(doc.proto_embeddings)].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail) : doc;
+ bestEmbedding && Doc.AddDocToList(Doc.GetProto(doc), 'protoEmbeddings', doc);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -746,7 +731,9 @@ export namespace Doc {
}
};
const docAtKey = doc[key];
- if (docAtKey instanceof Doc) {
+ if (key === 'author') {
+ assignKey(Doc.CurrentUserEmail);
+ } else if (docAtKey instanceof Doc) {
if (pruneDocs.includes(docAtKey)) {
// prune doc and do nothing
} else if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || ['embedContainer', 'annotationOn', 'proto'].includes(key) || ((key === 'link_anchor_1' || key === 'link_anchor_2') && doc.author === Doc.CurrentUserEmail))) {
@@ -776,11 +763,10 @@ export namespace Doc {
linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks));
}
});
- Doc.SetInPlace(copy, 'title', 'CLONE: ' + doc.title, true);
+ Doc.SetInPlace(copy, 'title', '>:' + doc.title, true);
copy.cloneOf = doc;
cloneMap.set(doc[Id], copy);
- Doc.AddFileOrphan(copy);
return copy;
}
export function repairClone(clone: Doc, cloneMap: Map<string, Doc>, visited: Set<Doc>) {
@@ -915,7 +901,7 @@ export namespace Doc {
// 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 expandedLayoutFieldKey = templateField + '-layout[' + templateLayoutDoc[Id] + ']';
+ const expandedLayoutFieldKey = templateField + '_layout[' + templateLayoutDoc[Id] + ']';
let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
@@ -934,6 +920,7 @@ export namespace Doc {
newLayoutDoc.rootDocument = targetDoc;
const dataDoc = Doc.GetProto(targetDoc);
newLayoutDoc.resolvedDataDoc = dataDoc;
+ newLayoutDoc['acl-Guest'] = SharingPermissions.Edit;
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 });
}
@@ -982,6 +969,59 @@ export namespace Doc {
return overwrite;
}
+ export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
+ if (infield instanceof List<any>) {
+ infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+ return;
+ }
+ const doc = infield as Doc;
+ if (references.has(doc)) {
+ references.add(doc);
+ return;
+ }
+ const excludeLists = doc.title === 'My Recently Closed' || doc.title === 'My Header Bar' || doc.title === 'My Dashboards';
+ if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return;
+ references.add(doc);
+ Object.keys(doc).forEach(key => {
+ if (key === 'proto') {
+ if (doc.proto instanceof Doc) {
+ Doc.FindReferences(doc.proto, references, system);
+ }
+ } else {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ if (field instanceof RefField) {
+ if (field instanceof Doc) {
+ if (key === 'myLinkDatabase') {
+ field instanceof Doc && references.add(field);
+ // skip docs that have been closed and are scheduled for garbage collection
+ } else {
+ Doc.FindReferences(field, references, system);
+ }
+ }
+ } else if (cfield instanceof ComputedField) {
+ } else if (field instanceof ObjectField) {
+ if (field instanceof Doc) {
+ Doc.FindReferences(field, references, system);
+ } else if (field instanceof List) {
+ !excludeLists && Doc.FindReferences(field, references, system);
+ } else if (field instanceof ProxyField) {
+ if (key === 'myLinkDatabase') {
+ field instanceof Doc && references.add(field);
+ // skip docs that have been closed and are scheduled for garbage collection
+ } else {
+ Doc.FindReferences(field.value, references, system);
+ }
+ } else if (field instanceof PrefetchProxy) {
+ Doc.FindReferences(field.value, references, system);
+ }
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happend...
+ }
+ }
+ });
+ }
+
export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
const copy = new Doc(copyProtoId, true);
updateCachedAcls(copy);
@@ -1019,11 +1059,9 @@ export namespace Doc {
Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy);
}
copy.embedContainer = undefined;
- Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared');
if (retitle) {
copy.title = incrementTitleCopy(StrCast(copy.title));
}
- Doc.AddFileOrphan(copy);
return copy;
}
@@ -1042,7 +1080,6 @@ export namespace Doc {
if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
- Doc.AddFileOrphan(delegate);
return delegate;
}
return undefined;
@@ -1079,7 +1116,6 @@ export namespace Doc {
const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
target.layout_fieldKey = targetKey;
applied && (Doc.GetProto(applied).type = templateDoc.type);
- Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared');
return applied;
}
return undefined;
@@ -1090,7 +1126,7 @@ export namespace Doc {
target[targetKey] = new PrefetchProxy(templateDoc);
} else {
titleTarget && (Doc.GetProto(target).title = titleTarget);
- const setDoc = [AclAdmin, AclEdit].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
+ const setDoc = [AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
setDoc[targetKey] = new PrefetchProxy(templateDoc);
}
}
@@ -1176,7 +1212,7 @@ export namespace Doc {
// 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[DocLayout] || doc;
}
export function SetLayout(doc: Doc, layout: Doc | string) {
@@ -1308,8 +1344,13 @@ export namespace Doc {
}
export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
- if (linkDoc.link_anchor_2 === anchorDoc || (linkDoc.link_anchor_2 as Doc).annotationOn) return '2';
- return Doc.AreProtosEqual(anchorDoc, (linkDoc.link_anchor_1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.link_anchor_1 as Doc) ? '1' : '2';
+ const linkAnchor2 = DocCast(linkDoc.link_anchor_2);
+ const linkAnchor1 = DocCast(linkDoc.link_anchor_1);
+ if (linkDoc.link_matchEmbeddings) {
+ return [linkAnchor2, linkAnchor2.annotationOn].includes(anchorDoc) ? '2' : '1';
+ }
+ if (Doc.AreProtosEqual(linkAnchor2, anchorDoc) || Doc.AreProtosEqual(linkAnchor2.annotationOn as Doc, anchorDoc)) return '2';
+ return Doc.AreProtosEqual(linkAnchor1, anchorDoc) || Doc.AreProtosEqual(linkAnchor1.annotationOn as Doc, anchorDoc) ? '1' : '2';
}
export function linkFollowUnhighlight() {
@@ -1327,9 +1368,9 @@ export namespace Doc {
UnhighlightWatchers.push(watcher);
} else watcher();
}
- export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) {
+ export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
linkFollowUnhighlight();
- (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect));
+ (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
document.addEventListener('pointerdown', linkFollowUnhighlight);
if (UnhighlightTimer) clearTimeout(UnhighlightTimer);
@@ -1344,11 +1385,11 @@ export namespace Doc {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
return doc[Highlight] || Doc.GetProto(doc)[Highlight];
}
- export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) {
+ export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) {
runInAction(() => {
highlightedDocs.add(doc);
doc[Highlight] = true;
- doc[Animation] = presEffect;
+ doc[Animation] = presentation_effect;
if (dataAndDisplayDocs) {
highlightedDocs.add(Doc.GetProto(doc));
Doc.GetProto(doc)[Highlight] = true;
@@ -1430,6 +1471,8 @@ export namespace Doc {
}
}
+ export const FilterSep = '::';
+
// 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
@@ -1439,7 +1482,7 @@ export namespace Doc {
const childFilters = StrListCast(container[filterField]);
runInAction(() => {
for (let i = 0; i < childFilters.length; i++) {
- const fields = childFilters[i].split(':'); // split key:value:modifier
+ const fields = childFilters[i].split(FilterSep); // split key:value:modifier
if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
if (fields[2] === modifiers && modifiers && fields[1] === value) {
if (toggle) modifiers = 'remove';
@@ -1454,7 +1497,7 @@ export namespace Doc {
container[filterField] = undefined;
} else if (modifiers !== 'remove') {
!append && (childFilters.length = 0);
- childFilters.push(key + ':' + value + ':' + modifiers);
+ childFilters.push(key + FilterSep + value + FilterSep + modifiers);
container[filterField] = new List<string>(childFilters);
}
});
@@ -1534,13 +1577,13 @@ export namespace Doc {
// prettier-ignore
export function toIcon(doc?: Doc, isOpen?: boolean) {
- switch (StrCast(doc?.type)) {
+ switch (isOpen !== undefined ? DocumentType.COL: StrCast(doc?.type)) {
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 === true ? 'folder-open' : isOpen === false ? 'folder' : 'question';
+ const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question';
return !doc?.isFolder ? folder : chevron;
case DocumentType.WEB: return 'globe-asia';
case DocumentType.SCREENSHOT: return 'photo-video';
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 65decc147..088903082 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -1,4 +1,4 @@
-export const Update = Symbol('DocUpdate');
+export const DocUpdated = Symbol('DocUpdated');
export const Self = Symbol('DocSelf');
export const SelfProxy = Symbol('DocSelfProxy');
export const FieldKeys = Symbol('DocFieldKeys');
@@ -13,7 +13,6 @@ export const DocFields = Symbol('DocFields');
export const DocCss = Symbol('DocCss');
export const DocAcl = Symbol('DocAcl');
export const DirectLinks = Symbol('DocDirectLinks');
-export const AclUnset = Symbol('DocAclUnset');
export const AclPrivate = Symbol('DocAclOwnerOnly');
export const AclReadonly = Symbol('DocAclReadOnly');
export const AclAugment = Symbol('DocAclAugment');
@@ -24,3 +23,5 @@ export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
export const Initializing = Symbol('DocInitializing');
export const ForceServerWrite = Symbol('DocForceServerWrite');
export const CachedUpdates = Symbol('DocCachedUpdates');
+
+export const DashVersion = 'v0.5.6';
diff --git a/src/fields/FieldLoader.tsx b/src/fields/FieldLoader.tsx
index 2a7b936f7..a5a71833c 100644
--- a/src/fields/FieldLoader.tsx
+++ b/src/fields/FieldLoader.tsx
@@ -6,10 +6,9 @@ import './FieldLoader.scss';
@observer
export class FieldLoader extends React.Component {
- @observable public static ServerLoadStatus = { requested: 0, retrieved: 0 };
- public static active = false;
+ @observable public static ServerLoadStatus = { requested: 0, retrieved: 0, message: '' };
render() {
- return <div className="fieldLoader">{`Requested: ${FieldLoader.ServerLoadStatus.requested} ... ${FieldLoader.ServerLoadStatus.retrieved} `}</div>;
+ return <div className="fieldLoader">{`${FieldLoader.ServerLoadStatus.message} request: ${FieldLoader.ServerLoadStatus.requested} ... ${FieldLoader.ServerLoadStatus.retrieved} `}</div>;
}
}
diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts
index c381f14f5..0dbeb064b 100644
--- a/src/fields/FieldSymbols.ts
+++ b/src/fields/FieldSymbols.ts
@@ -1,6 +1,6 @@
export const HandleUpdate = Symbol('FieldHandleUpdate');
export const Id = Symbol('FieldId');
-export const OnUpdate = Symbol('FieldOnUpdate');
+export const FieldChanged = Symbol('FieldChanged');
export const Parent = Symbol('FieldParent');
export const Copy = Symbol('FieldCopy');
export const ToValue = Symbol('FieldToValue');
diff --git a/src/fields/IconField.ts b/src/fields/IconField.ts
deleted file mode 100644
index 76c4ddf1b..000000000
--- a/src/fields/IconField.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, primitive } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-
-@Deserializable("icon")
-export class IconField extends ObjectField {
- @serializable(primitive())
- readonly icon: string;
-
- constructor(icon: string) {
- super();
- this.icon = icon;
- }
-
- [Copy]() {
- return new IconField(this.icon);
- }
-
- [ToScriptString]() {
- return "invalid";
- }
- [ToString]() {
- return "ICONfield";
- }
-}
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 033fa569b..f3fcc87f7 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,219 +1,17 @@
-import { action, observable } from 'mobx';
+import { action, computed, 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 { FieldTuples, Self, SelfProxy, Update } from './DocSymbols';
import { Field } from './Doc';
-import { Copy, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { FieldTuples, Self, SelfProxy } from './DocSymbols';
+import { Copy, FieldChanged, Parent, ToScriptString, ToString } 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');
- },
- fill(value: any, start?: number, end?: number) {
- if (value instanceof RefField) {
- throw new Error('fill with RefFields not supported yet');
- }
- const res = this[Self].__fieldTuples.fill(value, start, end);
- this[Update]();
- return res;
- },
- pop(): any {
- const field = toRealField(this[Self].__fieldTuples.pop());
- this[Update]();
- return field;
- },
- push: action(function (this: any, ...items: any[]) {
- items = items.map(toObjectField);
-
- const list = this[Self];
- const length = list.__fieldTuples.length;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + length, item, this);
- }
- }
- const res = list.__fieldTuples.push(...items);
- this[Update]({ op: '$addToSet', items, length: length + items.length });
- return res;
- }),
- reverse() {
- const res = this[Self].__fieldTuples.reverse();
- this[Update]();
- return res;
- },
- shift() {
- const res = toRealField(this[Self].__fieldTuples.shift());
- this[Update]();
- return res;
- },
- sort(cmpFunc: any) {
- this[Self].__realFields(); // coerce retrieving entire array
- const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
- this[Update]();
- return res;
- },
- splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
- this[Self].__realFields(); // coerce retrieving entire array
- items = items.map(toObjectField);
- const list = this[Self];
- const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + start, item, this);
- }
- }
- let hintArray: { val: any; index: number }[] = [];
- for (let i = start; i < start + deleteCount; i++) {
- hintArray.push({ val: list.__fieldTuples[i], index: i });
- }
- const res = list.__fieldTuples.splice(start, deleteCount, ...items);
- // the hint object sends the starting index of the slice and the number
- // of elements to delete.
- this[Update](
- items.length === 0 && deleteCount
- ? { op: '$remFromSet', items: removed, hint: { start: start, deleteCount: deleteCount }, length: list.__fieldTuples.length }
- : items.length && !deleteCount && start === list.__fieldTuples.length
- ? { op: '$addToSet', items, length: list.__fieldTuples.length }
- : undefined
- );
- return res.map(toRealField);
- }),
- unshift(...items: any[]) {
- items = items.map(toObjectField);
- const list = this[Self];
- const length = list.__fieldTuples.length;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i, item, this);
- }
- }
- const res = this[Self].__fieldTuples.unshift(...items);
- this[Update]();
- return res;
- },
- /// Accessor methods
- concat: action(function (this: any, ...items: any[]) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).concat(...items);
- }),
- includes(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().includes(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
- }
- },
- indexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().indexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
- }
- },
- join(separator: any) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).join(separator);
- },
- lastElement() {
- return this[Self].__realFields().lastElement();
- },
- lastIndexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
- }
- },
- slice(begin: number, end: number) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
- },
-
- /// Iteration methods
- entries() {
- return this[Self].__realFields().entries();
- },
- every(callback: any, thisArg: any) {
- return this[Self].__realFields().every(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- filter(callback: any, thisArg: any) {
- return this[Self].__realFields().filter(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- find(callback: any, thisArg: any) {
- return this[Self].__realFields().find(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- findIndex(callback: any, thisArg: any) {
- return this[Self].__realFields().findIndex(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- forEach(callback: any, thisArg: any) {
- return this[Self].__realFields().forEach(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- map(callback: any, thisArg: any) {
- return this[Self].__realFields().map(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- reduce(callback: any, initialValue: any) {
- return this[Self].__realFields().reduce(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- reduceRight(callback: any, initialValue: any) {
- return this[Self].__realFields().reduceRight(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- some(callback: any, thisArg: any) {
- return this[Self].__realFields().some(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- values() {
- return this[Self].__realFields().values();
- },
- [Symbol.iterator]() {
- return this[Self].__realFields().values();
- },
-};
+import { deleteProperty, getter, setter, containedFieldChangedHandler } from './util';
function toObjectField(field: Field) {
return field instanceof RefField ? new ProxyField(field) : field;
@@ -223,38 +21,221 @@ function toRealField(field: Field) {
return field instanceof ProxyField ? field.value : field;
}
-function listGetter(target: any, prop: string | symbol, receiver: any): any {
- if (listHandlers.hasOwnProperty(prop)) {
- return listHandlers[prop];
- }
- return getter(target, prop, receiver);
-}
-
-interface ListSpliceUpdate<T> {
- type: 'splice';
- index: number;
- added: T[];
- removedCount: number;
-}
-
-interface ListIndexUpdate<T> {
- type: 'update';
- index: number;
- newValue: T;
-}
-
-type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
-
type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
export const ListFieldName = 'fields';
@Deserializable('list')
class ListImpl<T extends Field> extends ObjectField {
+ static listHandlers: any = {
+ /// Mutator methods
+ copyWithin() {
+ 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');
+ }
+ const res = this[Self].__fieldTuples.fill(value, start, end);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ pop(): any {
+ const field = toRealField(this[Self].__fieldTuples.pop());
+ this[SelfProxy][FieldChanged]?.();
+ return field;
+ },
+ push: action(function (this: ListImpl<any>, ...items: any[]) {
+ items = items.map(toObjectField);
+
+ const list = this[Self];
+ const length = list.__fieldTuples.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], i + length, item);
+ }
+ }
+ const res = list.__fieldTuples.push(...items);
+ this[SelfProxy][FieldChanged]?.({ op: '$addToSet', items, length: length + items.length });
+ return res;
+ }),
+ reverse() {
+ const res = this[Self].__fieldTuples.reverse();
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ shift() {
+ const res = toRealField(this[Self].__fieldTuples.shift());
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ sort(cmpFunc: any) {
+ this[Self].__realFields; // coerce retrieving entire array
+ const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ this[Self].__realFields; // coerce retrieving entire array
+ items = items.map(toObjectField);
+ const list = this[Self];
+ const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i + start, item);
+ }
+ }
+ let hintArray: { val: any; index: number }[] = [];
+ for (let i = start; i < start + deleteCount; i++) {
+ hintArray.push({ val: list.__fieldTuples[i], index: i });
+ }
+ const res = list.__fieldTuples.splice(start, deleteCount, ...items);
+ // the hint object sends the starting index of the slice and the number
+ // of elements to delete.
+ this[SelfProxy][FieldChanged]?.(
+ items.length === 0 && deleteCount
+ ? { op: '$remFromSet', items: removed, hint: { start, deleteCount }, length: list.__fieldTuples.length }
+ : items.length && !deleteCount && start === list.__fieldTuples.length
+ ? { op: '$addToSet', items, length: list.__fieldTuples.length }
+ : undefined
+ );
+ return res.map(toRealField);
+ }),
+ unshift(...items: any[]) {
+ items = items.map(toObjectField);
+ const list = this[Self];
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i, item);
+ }
+ }
+ const res = this[Self].__fieldTuples.unshift(...items);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ /// Accessor methods
+ concat: action(function (this: any, ...items: any[]) {
+ this[Self].__realFields;
+ return this[Self].__fieldTuples.map(toRealField).concat(...items);
+ }),
+ includes(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields.includes(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
+ }
+ },
+ indexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields.indexOf(valueToFind, fromIndex);
+ }
+ return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
+ },
+ join(separator: any) {
+ this[Self].__realFields;
+ return this[Self].__fieldTuples.map(toRealField).join(separator);
+ },
+ lastElement() {
+ return this[Self].__realFields.lastElement();
+ },
+ lastIndexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields.lastIndexOf(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
+ }
+ },
+ slice(begin: number, end: number) {
+ this[Self].__realFields;
+ return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
+ },
+
+ /// Iteration methods
+ entries() {
+ return this[Self].__realFields.entries();
+ },
+ every(callback: any, thisArg: any) {
+ return this[Self].__realFields.every(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ filter(callback: any, thisArg: any) {
+ return this[Self].__realFields.filter(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ find(callback: any, thisArg: any) {
+ return this[Self].__realFields.find(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ findIndex(callback: any, thisArg: any) {
+ return this[Self].__realFields.findIndex(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ forEach(callback: any, thisArg: any) {
+ return this[Self].__realFields.forEach(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ map(callback: any, thisArg: any) {
+ return this[Self].__realFields.map(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ reduce(callback: any, initialValue: any) {
+ return this[Self].__realFields.reduce(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ reduceRight(callback: any, initialValue: any) {
+ return this[Self].__realFields.reduceRight(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ some(callback: any, thisArg: any) {
+ return this[Self].__realFields.some(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ values() {
+ return this[Self].__realFields.values();
+ },
+ [Symbol.iterator]() {
+ return this[Self].__realFields.values();
+ },
+ };
+ static listGetter(target: any, prop: string | symbol, receiver: any): any {
+ if (ListImpl.listHandlers.hasOwnProperty(prop)) {
+ return ListImpl.listHandlers[prop];
+ }
+ return getter(target, prop, receiver);
+ }
constructor(fields?: T[]) {
super();
const list = new Proxy<this>(this, {
set: setter,
- get: listGetter,
+ get: ListImpl.listGetter,
ownKeys: target => Object.keys(target.__fieldTuples),
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target[FieldTuples]) {
@@ -270,9 +251,9 @@ class ListImpl<T extends Field> extends ObjectField {
throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
},
});
- this[SelfProxy] = list;
+ this[SelfProxy] = list as any as List<Field>; // bcz: ugh .. don't know how to convince typesecript that list is a List
if (fields) {
- (list as any).push(...fields);
+ this[SelfProxy].push(...fields);
}
return list;
}
@@ -281,7 +262,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() {
+ @computed private get __realFields() {
const unrequested = this[FieldTuples].filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>);
// if we find any ProxyFields that don't have a current value, then
// start the server request for all of them
@@ -305,10 +286,10 @@ class ListImpl<T extends Field> extends ObjectField {
private set __fieldTuples(value) {
this[FieldTuples] = value;
for (const key in value) {
- const field = value[key];
- if (field instanceof ObjectField) {
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ const item = value[key];
+ if (item instanceof ObjectField) {
+ item[Parent] = this[Self];
+ item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], Number(key), item);
}
}
}
@@ -322,16 +303,8 @@ class ListImpl<T extends Field> extends ObjectField {
// @serializable(alias("fields", list(autoObject())))
@observable
private [FieldTuples]: StoredType<T>[] = [];
-
- private [Update] = (diff: any) => {
- // console.log(diff);
- const update = this[OnUpdate];
- // update && update(diff);
- update?.(diff);
- };
-
private [Self] = this;
- private [SelfProxy]: any;
+ private [SelfProxy]: List<Field>; // also used in utils.ts even though it won't be found using find all references
[ToScriptString]() {
return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index daa8a7777..b5bc2952a 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -1,9 +1,14 @@
-import { RefField } from "./RefField";
-import { OnUpdate, Parent, Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { ScriptingGlobals } from "../client/util/ScriptingGlobals";
+import { RefField } from './RefField';
+import { FieldChanged, Parent, Copy, ToScriptString, ToString } from './FieldSymbols';
+import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { Field } from './Doc';
export abstract class ObjectField {
- public [OnUpdate]?: (diff?: any) => void;
+ // prettier-ignore
+ public [FieldChanged]?: (diff?: { op: '$addToSet' | '$remFromSet' | '$set';
+ items: Field[] | undefined;
+ length: number | undefined;
+ hint?: any }, serverOp?: any) => void;
public [Parent]?: RefField | ObjectField;
abstract [Copy](): ObjectField;
@@ -17,4 +22,4 @@ export namespace ObjectField {
}
}
-ScriptingGlobals.add(ObjectField); \ No newline at end of file
+ScriptingGlobals.add(ObjectField);
diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts
deleted file mode 100644
index f236a04fd..000000000
--- a/src/fields/PresField.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-//insert code here
-import { ObjectField } from "./ObjectField";
-
-export abstract class PresField extends ObjectField {
-
-} \ No newline at end of file
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 55d1d9ea4..c076f5fe1 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,5 +1,5 @@
import { Deserializable } from '../client/util/SerializationHelper';
-import { FieldWaiting, Opt } from './Doc';
+import { Field, FieldWaiting, Opt } from './Doc';
import { primitive, serializable } from 'serializr';
import { observable, action, runInAction, computed } from 'mobx';
import { DocServer } from '../client/DocServer';
@@ -39,7 +39,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
}
[ToScriptString]() {
- return 'invalid';
+ return Field.toScriptString(this[ToValue](undefined)?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
}
[ToString]() {
return 'ProxyField';
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index 4b1855cb0..6dde2e5aa 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -1,7 +1,7 @@
import { Deserializable } from '../client/util/SerializationHelper';
import { serializable, primitive } from 'serializr';
import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString, OnUpdate } from './FieldSymbols';
+import { Copy, ToScriptString, ToString, FieldChanged } from './FieldSymbols';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView';
@@ -82,32 +82,32 @@ export class SchemaHeaderField extends ObjectField {
setHeading(heading: string) {
this.heading = heading;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setColor(color: string) {
this.color = color;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setType(type: ColumnType) {
this.type = type;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setWidth(width: number) {
this.width = width;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setDesc(desc: boolean | undefined) {
this.desc = desc;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setCollapsed(collapsed: boolean | undefined) {
this.collapsed = collapsed;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
[Copy]() {
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 251b1149d..69dbe9756 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,11 +1,10 @@
-import { Field, Opt, FieldResult, Doc } from './Doc';
+import { DateField } from './DateField';
+import { Doc, Field, FieldResult, Opt } from './Doc';
import { List } from './List';
import { RefField } from './RefField';
-import { DateField } from './DateField';
-import { ScriptField } from './ScriptField';
-import { URLField, WebField, ImageField, CsvField } from './URLField';
-import { TextField } from '@material-ui/core';
import { RichTextField } from './RichTextField';
+import { ScriptField } from './ScriptField';
+import { CsvField, ImageField, WebField } from './URLField';
export type ToType<T extends InterfaceValue> = T extends 'string'
? string
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 757d507be..e33a17416 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -72,11 +72,11 @@ export const documentSchema = createSchema({
stroke_endMarker: 'string',
stroke_dash: 'string',
textTransform: 'string',
- treeViewOpen: 'boolean', // flag denoting whether the documents sub-tree (contents) is visible or hidden
- treeViewExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree
- treeViewExpandedViewLock: 'boolean', // whether the expanded view can be changed
- treeViewOpenIsTransient: 'boolean', // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
- treeViewType: 'string', // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
+ treeView_Open: 'boolean', // flag denoting whether the documents sub-tree (contents) is visible or hidden
+ treeView_ExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree
+ treeView_ExpandedViewLock: 'boolean', // whether the expanded view can be changed
+ treeView_OpenIsTransient: 'boolean', // ignores the treeView_Open flag (for allowing a view to not be slaved to other views of the document)
+ treeView_Type: 'string', // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
// interaction and linking properties
ignoreClick: 'boolean', // whether documents ignores input clicks (but does not ignore manipulation and other events)
@@ -95,12 +95,12 @@ export const documentSchema = createSchema({
link_displayArrow: 'boolean', // toggles directed arrows
// drag drop properties
- _stayInCollection: 'boolean', // whether document can be dropped into a different collection
+ _dragOnlyWithinContainer: 'boolean', // whether document can be dropped into a different collection
dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
- dropAction: 'string', // override specifying what should happen when this document is dropped (can be "embed", "copy", "move")
- targetDropAction: 'string', // allows the target of a drop event to specify the dropAction ("embed", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
- childDropAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
- removeDropProperties: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
+ dropAction: 'string', // override specifying what should happen when something is dropped on this document (can be "embed", "copy", "move")
+ dragAction: 'string', // override specifying what should happen when this document s dragged (can be "embed", "copy", "move")
+ childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
+ dropPropertiesToRemove: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
});
export const collectionSchema = createSchema({
diff --git a/src/fields/util.ts b/src/fields/util.ts
index f365adf4b..28db77c65 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,23 +1,21 @@
import { $mobx, action, observable, runInAction, trace } from 'mobx';
import { computedFn } from 'mobx-utils';
import { DocServer } from '../client/DocServer';
-import { CollectionViewType } from '../client/documents/DocumentTypes';
import { LinkManager } from '../client/util/LinkManager';
import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
import { returnZero } from '../Utils';
-import CursorField from './CursorField';
-import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, updateCachedAcls } from './Doc';
-import { AclAdmin, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols';
-import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols';
+import { aclLevel, Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
+import { FieldChanged, Id, Parent, ToValue } 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, ScriptField } from './ScriptField';
-import { ScriptCast, StrCast } from './Types';
+import { ComputedField } from './ScriptField';
+import { DocCast, ScriptCast, StrCast } from './Types';
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -34,9 +32,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
return true;
}
- if (value !== undefined) {
- value = value[SelfProxy] || value;
- }
+ value = value?.[SelfProxy] ?? value; // convert any Doc type values to Proxy's
+
const curValue = target.__fieldTuples[prop];
if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
// TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
@@ -52,11 +49,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
throw new Error("Can't put the same object in multiple documents at the same time");
}
value[Parent] = receiver;
- value[OnUpdate] = updateFunction(target, prop, value, receiver);
+ value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value);
}
if (curValue instanceof ObjectField) {
delete curValue[Parent];
- delete curValue[OnUpdate];
+ delete curValue[FieldChanged];
}
const effectiveAcl = GetEffectiveAcl(target);
@@ -64,8 +61,11 @@ 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 writeToDoc =
+ sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField);
+ const writeToServer =
+ !DocServer.Control.isReadOnly() && //
+ (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && value instanceof RichTextField));
if (writeToDoc) {
if (value === undefined) {
@@ -79,14 +79,16 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
}
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 } });
+ // prettier-ignore
+ if (value === undefined)
+ (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $unset: { ['fields.' + prop]: '' } });
+ else (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) :value}});
if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target);
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
}
!receiver[Initializing] &&
- !receiver.dontUndo &&
+ !StrListCast(receiver.undoIgnoreFields).includes(prop.toString()) &&
(!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
UndoManager.AddEvent(
{
@@ -106,7 +108,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
);
return true;
}
- return false;
+ return true;
});
let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl;
@@ -129,14 +131,18 @@ export function denormalizeEmail(email: string) {
/**
* 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.defaultAclPrivate ? AclPrivate : dataDoc[key];
- // key.startsWith('acl') && distributeAcls(key, permission, child);
- // }
+export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) {
+ [...Object.keys(parent), ...(Doc.CurrentUserEmail !== parent.author ? ['acl-Owner'] : [])]
+ .filter(key => key.startsWith('acl'))
+ .forEach(key => {
+ // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private.
+ // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key];
+ const parAcl = ReverseHierarchyMap.get(StrCast(key === 'acl-Owner' ? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Edit) : parent[key]))?.acl;
+ if (parAcl) {
+ const sharePermission = HierarchyMapping.get(parAcl)?.name;
+ sharePermission && distributeAcls(key === 'acl-Owner' ? `acl-${normalizeEmail(StrCast(parent.author))}` : key, sharePermission, child, undefined, false, layoutOnly);
+ }
+ });
}
/**
@@ -155,13 +161,11 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
* Unset: Remove a sharing permission (eg., used )
*/
export enum SharingPermissions {
- Unset = 'None',
Admin = 'Admin',
Edit = 'Edit',
- SelfEdit = 'Self Edit',
Augment = 'Augment',
View = 'View',
- None = 'Not Shared',
+ None = 'Not-Shared',
}
// return acl from cache or cache the acl and return.
@@ -174,11 +178,11 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
*/
export function GetEffectiveAcl(target: any, user?: string): symbol {
if (!target) return AclPrivate;
- if (target[UpdatingFromServer]) return AclAdmin;
+ if (target[UpdatingFromServer] || Doc.CurrentUserEmail === 'guest') return AclAdmin;
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) {
+export function GetPropAcl(target: any, prop: string | symbol | number) {
if (typeof prop === 'symbol' || target[UpdatingFromServer]) 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);
@@ -205,18 +209,13 @@ function getEffectiveAcl(target: any, user?: string): symbol {
// there are issues with storing fields with . in the name, so they are replaced with _ during creation
// 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)!.level > HierarchyMapping.get(effectiveAcl)!.level) {
- if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') {
+ if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') {
+ if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) {
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'];
- // if (override !== AclUnset && override !== undefined) effectiveAcl = override;
-
- // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl)
return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
}
// authored documents are private until an ACL is set.
@@ -224,73 +223,72 @@ function getEffectiveAcl(target: any, user?: string): symbol {
if (targetAuthor && targetAuthor !== userChecked) return AclPrivate;
return AclAdmin;
}
+
/**
* Recursively distributes the access right for a user across the children of a document and its annotations.
* @param key the key storing the access right (e.g. acl-groupname)
* @param acl the access right being stored (e.g. "Can Edit")
* @param target the document on which this access right is being set
- * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection)
- * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change
+ * @param visited list of Doc's already distributed to.
+ * @param allowUpgrade whether permissions can be made less restrictive
+ * @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well)
*/
-export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) {
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
+ const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`;
if (!visited) visited = [] as Doc[];
- if (!target || visited.includes(target)) return;
-
- if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) {
- target[key] = acl;
- if (target !== Doc.GetProto(target)) {
- //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???)
- updateCachedAcls(target);
- }
- return;
- }
+ if (!target || visited.includes(target) || key === selfKey) return;
visited.push(target);
- let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
- // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive
- if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) {
- target[key] = acl;
- layoutDocChanged = true;
-
- if (isDashboard) {
- DocListCastAsync(target[Doc.LayoutFieldKey(target)]).then(docs => {
- docs?.forEach(d => distributeAcls(key, acl, d, inheritingFromCollection, visited));
- });
- }
- }
-
let dataDocChanged = false;
const dataDoc = target[DocData];
- if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) {
- if (GetEffectiveAcl(dataDoc) === AclAdmin) {
- dataDoc[key] = acl;
- dataDocChanged = true;
- }
+ const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0;
+ const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0;
+ if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) {
+ // propagate ACLs to links, children, and annotations
- // maps over the links of the document
- LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
+ LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false));
- // maps over the children of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
- distributeAcls(key, acl, d, inheritingFromCollection, visited);
- distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
+ distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
});
- // maps over the annotations of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
- distributeAcls(key, acl, d, inheritingFromCollection, visited);
- distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
+ distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
});
+
+ Object.keys(target) // share expanded layout templates (eg, for presElementBox'es )
+ .filter(lkey => lkey.includes('layout[') && DocCast(target[lkey]))
+ .map(lkey => {
+ distributeAcls(key, acl, DocCast(target[lkey]), visited, allowUpgrade ? true : false);
+ });
+
+ if (GetEffectiveAcl(dataDoc) === AclAdmin) {
+ dataDoc[key] = acl;
+ dataDocChanged = true;
+ }
+ }
+
+ let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
+ // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive
+ if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !Doc.GetT(target, key, 'boolean', true) || ReverseHierarchyMap.get(StrCast(target[key]))!.level > aclVal)) {
+ target[key] = acl;
+ layoutDocChanged = true;
+ if (dataDoc[key] === undefined) dataDoc[key] = acl;
}
layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made
dataDocChanged && updateCachedAcls(dataDoc);
}
+//
+// target should be either a Doc or ListImpl. receiver should be a Proxy<Doc> Or List.
+//
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
- const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : getPropAcl(target, prop);
- if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
+ const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
+ if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) 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].includes(value))) return true;
@@ -357,85 +355,104 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
return true;
}
-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)), hint: diff.hint } }
- : { $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] &&
+// this function creates a function that can be used to setup Undo for whenever an ObjectField changes.
+// the idea is that the Doc field setter can only setup undo at the granularity of an entire field and won't even be called if
+// just a part of a field (eg. field within an ObjectField) changes. This function returns a function that can be called
+// whenever an internal ObjectField field changes. It should be passed a 'diff' specification describing the change. Currently,
+// List's are the only true ObjectFields that can be partially modified (ignoring SchemaHeaderFields which should go away).
+// The 'diff' specification that a list can send is limited to indicating that something was added, removed, or that the list contents
+// were replaced. Based on this specification, an Undo event is setup that will save enough information about the ObjectField to be
+// able to undo and redo the partial change.
+//
+export function containedFieldChangedHandler(container: List<Field> | Doc, prop: string | number, liveContainedField: ObjectField) {
+ let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField;
+ return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => {
+ const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: Field) => SerializationHelper.Serialize(item)) });
+ // prettier-ignore
+ const serverOp = diff?.op === '$addToSet'
+ ? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length }
+ : diff?.op === '$remFromSet'
+ ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint}, length: diff.length }
+ : { $set: { ['fields.' + prop]: liveContainedField ? SerializationHelper.Serialize(liveContainedField) : undefined } };
+
+ if (!(container instanceof Doc) || !container[UpdatingFromServer]) {
+ const prevValue = ObjectField.MakeCopy(lastValue as List<any>);
+ lastValue = ObjectField.MakeCopy(liveContainedField);
+ const newValue = ObjectField.MakeCopy(liveContainedField);
+ if (diff?.op === '$addToSet') {
+ UndoManager.AddEvent(
+ {
+ redo: () => {
+ //console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
+ (container as any)[prop as any]?.push(...(diff.items || [])?.map((item: any) => item.value ?? item));
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ undo: action(() => {
+ // console.log('undo $add: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ const ind =
+ item instanceof SchemaHeaderField //
+ ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading)
+ : (container as any)[prop as any]?.indexOf(item.value ?? item);
+ ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ }),
+ prop: 'add ' + diff.items?.length + ' items to list',
+ },
+ diff?.items
+ );
+ } else if (diff?.op === '$remFromSet') {
UndoManager.AddEvent(
- diff?.op === '$addToSet'
- ? {
- redo: () => {
- console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
- receiver[prop].push(...diff.items.map((item: any) => 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) => {
- if (item instanceof SchemaHeaderField) {
- const ind = receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
- ind !== -1 && receiver[prop].splice(ind, 1);
- } else {
- const ind = receiver[prop].indexOf(item.value ?? item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- }
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- prop: 'add ' + diff.items.length + ' items to list',
- }
- : diff?.op === '$remFromSet'
- ? {
- redo: action(() => {
- console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
- 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);
- 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);
- ind !== -1 && receiver[prop].indexOf(item.value ?? item) === -1 && receiver[prop].splice(ind, 0, item);
- }
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: 'remove ' + diff.items.length + ' items from list',
- }
- : {
- redo: () => {
- console.log('redo list: ' + prop, receiver[prop]); // bcz: uncomment to log undo
- 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: 'assign list',
- }
+ {
+ redo: action(() => {
+ // console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ const ind =
+ item instanceof SchemaHeaderField //
+ ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading)
+ : (container as any)[prop as any]?.indexOf(item.value ?? item);
+ ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ }),
+ 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 && (container as any)[prop as any].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && (container as any)[prop as any].splice(ind, 0, item);
+ } else {
+ const ind = (prevValue as List<any>).indexOf(item.value ?? item);
+ ind !== -1 && (container as any)[prop as any].indexOf(item.value ?? item) === -1 && (container as any)[prop as any].splice(ind, 0, item);
+ }
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ prop: 'remove ' + diff.items?.length + ' items from list',
+ },
+ diff?.items
);
+ } else {
+ const setFieldVal = (val: Field | undefined) => (container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as Field));
+ UndoManager.AddEvent(
+ {
+ redo: () => {
+ // console.log('redo list: ' + prop, fieldVal()); // bcz: uncomment to log undo
+ setFieldVal(newValue instanceof ObjectField ? ObjectField.MakeCopy(newValue) : undefined);
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ undo: () => {
+ // console.log('undo list: ' + prop, fieldVal()); // bcz: uncomment to log undo
+ setFieldVal(prevValue instanceof ObjectField ? ObjectField.MakeCopy(prevValue) : undefined);
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ prop: 'set list field',
+ },
+ diff?.items
+ );
+ }
}
- target[Update](op);
+ container[FieldChanged]?.(undefined, serverOp);
};
}
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
index 6cbf86f77..dc3a73def 100644
--- a/src/mobile/MobileMain.tsx
+++ b/src/mobile/MobileMain.tsx
@@ -12,7 +12,7 @@ AssignAllExtensions();
const info = await CurrentUserUtils.loadCurrentUser();
DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + ' (mobile)');
await Docs.Prototypes.initialize();
- await CurrentUserUtils.loadUserDocument(info.id);
+ await CurrentUserUtils.loadUserDocument(info);
document.getElementById('root')!.addEventListener(
'wheel',
event => {
diff --git a/src/scraping/buxton/narratives/Theme - Chord Kbds.docx b/src/scraping/buxton/narratives/Theme - Chord Kbds.docx
deleted file mode 100644
index 439a7d975..000000000
--- a/src/scraping/buxton/narratives/Theme - Chord Kbds.docx
+++ /dev/null
Binary files differ
diff --git a/src/server/ApiManagers/AzureManager.ts b/src/server/ApiManagers/AzureManager.ts
new file mode 100644
index 000000000..12bb98ad0
--- /dev/null
+++ b/src/server/ApiManagers/AzureManager.ts
@@ -0,0 +1,67 @@
+import { ContainerClient, BlobServiceClient } from "@azure/storage-blob";
+import * as fs from "fs";
+import { Readable, Stream } from "stream";
+const AZURE_STORAGE_CONNECTION_STRING = process.env.AZURE_STORAGE_CONNECTION_STRING;
+
+export class AzureManager {
+ private _containerClient: ContainerClient;
+ private _blobServiceClient: BlobServiceClient;
+ private static _instance: AzureManager | undefined;
+
+ public static CONTAINER_NAME = "dashmedia";
+ public static STORAGE_ACCOUNT_NAME = "dashblobstore";
+
+ constructor() {
+ if (!AZURE_STORAGE_CONNECTION_STRING) {
+ throw new Error("Azure Storage Connection String Not Found");
+ }
+ this._blobServiceClient = BlobServiceClient.fromConnectionString(AZURE_STORAGE_CONNECTION_STRING);
+ this._containerClient = this.BlobServiceClient.getContainerClient(AzureManager.CONTAINER_NAME);
+ }
+
+ public static get Instance() {
+ return this._instance = this._instance ?? new AzureManager();
+ }
+
+ public get BlobServiceClient() {
+ return this._blobServiceClient;
+ }
+
+ public get ContainerClient() {
+ return this._containerClient;
+ }
+
+ public static UploadBlob(filename: string, filepath: string, filetype: string) {
+ const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename);
+ const blobOptions = { blobHTTPHeaders: { blobContentType: filetype }};
+ const stream = fs.createReadStream(filepath);
+ return blockBlobClient.uploadStream(stream, undefined, undefined, blobOptions);
+ }
+
+ public static UploadBlobStream(stream: Readable, filename: string, filetype: string) {
+ const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename);
+ const blobOptions = { blobHTTPHeaders: { blobContentType: filetype }};
+ return blockBlobClient.uploadStream(stream, undefined, undefined, blobOptions);
+ }
+
+ public static DeleteBlob(filename: string) {
+ const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename);
+ return blockBlobClient.deleteIfExists();
+ }
+
+ public static async GetBlobs() {
+ const foundBlobs = [];
+ for await (const blob of this.Instance.ContainerClient.listBlobsFlat()) {
+ console.log(`${blob.name}`);
+
+ const blobItem = {
+ url : `https://${AzureManager.STORAGE_ACCOUNT_NAME}.blob.core.windows.net/${AzureManager.CONTAINER_NAME}/${blob.name}`,
+ name : blob.name
+ }
+
+ foundBlobs.push(blobItem);
+ }
+
+ return foundBlobs;
+ }
+}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index ba6d7acfe..ebc9deab7 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -12,6 +12,7 @@ import { AcceptableMedia, Upload } from '../SharedMediaTypes';
import ApiManager, { Registration } from './ApiManager';
import { SolrManager } from './SearchManager';
import v4 = require('uuid/v4');
+import { DashVersion } from '../../fields/DocSymbols';
const AdmZip = require('adm-zip');
const imageDataUri = require('image-data-uri');
const fs = require('fs');
@@ -43,6 +44,14 @@ export default class UploadManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.POST,
+ subscription: '/ping',
+ secureHandler: async ({ req, res }) => {
+ _success(res, { message: DashVersion, date: new Date() });
+ },
+ });
+
+ register({
+ method: Method.POST,
subscription: '/concatVideos',
secureHandler: async ({ req, res }) => {
// req.body contains the array of server paths to the videos
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index c3dadd821..8b7994eac 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -5,7 +5,8 @@ import { msToTime } from '../ActionUtilities';
import * as bcrypt from 'bcrypt-nodejs';
import { Opt } from '../../fields/Doc';
import { WebSocket } from '../websocket';
-import { DashStats } from '../DashStats';
+import { resolvedPorts } from '../server_Initialization';
+import { DashVersion } from '../../fields/DocSymbols';
export const timeMap: { [id: string]: number } = {};
interface ActivityUnit {
@@ -68,7 +69,18 @@ export default class UserManager extends ApiManager {
register({
method: Method.GET,
subscription: '/getCurrentUser',
- secureHandler: ({ res, user: { _id, email, cacheDocumentIds } }) => res.send(JSON.stringify({ id: _id, email, cacheDocumentIds })),
+ secureHandler: ({ res, user }) =>
+ res.send(
+ JSON.stringify({
+ version: DashVersion,
+ userDocumentId: user.userDocumentId,
+ linkDatabaseId: user.linkDatabaseId,
+ sharingDocumentId: user.sharingDocumentId,
+ email: user.email,
+ cacheDocumentIds: user.cacheDocumentIds,
+ resolvedPorts,
+ })
+ ),
publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })),
});
diff --git a/src/server/DashSession/Session/utilities/session_config.ts b/src/server/DashSession/Session/utilities/session_config.ts
index bde98e9d2..266759929 100644
--- a/src/server/DashSession/Session/utilities/session_config.ts
+++ b/src/server/DashSession/Session/utilities/session_config.ts
@@ -120,7 +120,7 @@ export const defaultConfig: Configuration = {
color: "green"
}
},
- ports: { server: 3000 },
+ ports: { server: 1050 },
polling: {
route: "/",
intervalSeconds: 30,
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index eaaac4e6d..117981a7c 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -6,7 +6,7 @@ import { createReadStream, createWriteStream, existsSync, readFileSync, rename,
import * as path from 'path';
import { basename } from 'path';
import * as sharp from 'sharp';
-import { Stream } from 'stream';
+import { Readable, Stream } from 'stream';
import { filesDirectory, publicDirectory } from '.';
import { Opt } from '../fields/Doc';
import { ParsedPDF } from '../server/PdfTypes';
@@ -17,6 +17,8 @@ import { resolvedServerUrl } from './server_Initialization';
import { AcceptableMedia, Upload } from './SharedMediaTypes';
import request = require('request-promise');
import formidable = require('formidable');
+import { AzureManager } from './ApiManagers/AzureManager';
+import axios from 'axios';
const spawn = require('child_process').spawn;
const { exec } = require('child_process');
const parse = require('pdf-parse');
@@ -42,6 +44,10 @@ function isLocal() {
return /Dash-Web[0-9]*[\\\/]src[\\\/]server[\\\/]public[\\\/](.*)/;
}
+function usingAzure() {
+ return process.env.USE_AZURE === 'true';
+}
+
export namespace DashUploadUtils {
export interface Size {
width: number;
@@ -61,6 +67,9 @@ export namespace DashUploadUtils {
const size = 'content-length';
const type = 'content-type';
+ const BLOBSTORE_URL = process.env.BLOBSTORE_URL;
+ const RESIZE_FUNCTION_URL = process.env.RESIZE_FUNCTION_URL;
+
const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; //TODO:glr
export async function concatVideos(filePaths: string[]): Promise<Upload.AccessPathInfo> {
@@ -90,7 +99,7 @@ export namespace DashUploadUtils {
merge
.input(textFilePath)
.inputOptions(['-f concat', '-safe 0'])
- .outputOptions('-c copy')
+ // .outputOptions('-c copy')
//.videoCodec("copy")
.save(outputFilePath)
.on('error', (err: any) => {
@@ -134,7 +143,6 @@ export namespace DashUploadUtils {
export function uploadYoutube(videoId: string): Promise<Upload.FileResponse> {
return new Promise<Upload.FileResponse<Upload.FileInformation>>((res, rej) => {
- console.log('Uploading YouTube video: ' + videoId);
const name = videoId;
const path = name.replace(/^-/, '__') + '.mp4';
const finalPath = serverPathToFile(Directory.videos, path);
@@ -182,9 +190,10 @@ export namespace DashUploadUtils {
}
export async function upload(file: File, overwriteGuid?: string): Promise<Upload.FileResponse> {
+ const isAzureOn = usingAzure();
const { type, path, name } = file;
const types = type?.split('/') ?? [];
- uploadProgress.set(overwriteGuid ?? name, 'uploading'); // If the client sent a guid it uses to track upload progress, use that guid. Otherwise, use the file's name.
+ uploadProgress.set(overwriteGuid ?? name, 'uploading'); // If the client sent a guid it uses to track upload progress, use that guid. Otherwise, use the file's name.
const category = types[0];
let format = `.${types[1]}`;
@@ -478,17 +487,51 @@ export namespace DashUploadUtils {
};
}
+ /**
+ * UploadInspectedImage() takes an image with its metadata. If Azure is being used, this method will call the Azure function
+ * to execute the resizing. If Azure is not used, the function will begin to resize the image.
+ *
+ * @param metadata metadata object from InspectImage()
+ * @param filename the name of the file
+ * @param prefix the prefix to use, which will be set to '' if none is provided.
+ * @param cleanUp a boolean indicating if the files should be deleted after upload. True by default.
+ * @returns the accessPaths for the resized files.
+ */
export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = '', cleanUp = true): Promise<Upload.ImageInformation> => {
const { requestable, source, ...remaining } = metadata;
const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split('/')[1].toLowerCase()}`;
const { images } = Directory;
const information: Upload.ImageInformation = {
accessPaths: {
- agnostic: getAccessPaths(images, resolved),
+ agnostic: usingAzure()
+ ? {
+ client: BLOBSTORE_URL + `/${resolved}`,
+ server: BLOBSTORE_URL + `/${resolved}`,
+ }
+ : getAccessPaths(images, resolved),
},
...metadata,
};
- const writtenFiles = await outputResizedImages(() => request(requestable), resolved, pathToDirectory(Directory.images));
+ let writtenFiles: { [suffix: string]: string };
+
+ if (usingAzure()) {
+ if (!RESIZE_FUNCTION_URL) {
+ throw new Error('Resize function URL not provided.');
+ }
+
+ try {
+ const response = await axios.post(RESIZE_FUNCTION_URL, {
+ url: requestable,
+ filename: resolved,
+ });
+ writtenFiles = response.data.writtenFiles;
+ } catch (err) {
+ console.error(err);
+ writtenFiles = {};
+ }
+ } else {
+ writtenFiles = await outputResizedImages(() => request(requestable), resolved, pathToDirectory(Directory.images));
+ }
for (const suffix of Object.keys(writtenFiles)) {
information.accessPaths[suffix] = getAccessPaths(images, writtenFiles[suffix]);
}
@@ -533,6 +576,15 @@ export namespace DashUploadUtils {
force: true,
};
+ /**
+ * outputResizedImages takes in a readable stream and resizes the images according to the sizes defined at the top of this file.
+ *
+ * The new images will be saved to the server with the corresponding prefixes.
+ * @param streamProvider a Stream of the image to process, taken from the /parsed_files location
+ * @param outputFileName the basename (No suffix) of the outputted file.
+ * @param outputDirectory the directory to output to, usually Directory.Images
+ * @returns a map with suffixes as keys and resized filenames as values.
+ */
export async function outputResizedImages(streamProvider: () => Stream | Promise<Stream>, outputFileName: string, outputDirectory: string) {
const writtenFiles: { [suffix: string]: string } = {};
for (const { resizer, suffix } of resizers(path.extname(outputFileName))) {
@@ -549,11 +601,16 @@ export namespace DashUploadUtils {
return writtenFiles;
}
+ /**
+ * define the resizers to use
+ * @param ext the extension
+ * @returns an array of resizer functions from sharp
+ */
function resizers(ext: string): DashUploadUtils.ImageResizer[] {
return [
{ suffix: SizeSuffix.Original },
...Object.values(DashUploadUtils.Sizes).map(({ suffix, width }) => {
- let initial: sharp.Sharp | undefined = sharp().resize(width, undefined, { withoutEnlargement: true });
+ let initial: sharp.Sharp | undefined = sharp({ failOnError: false }).resize(width, undefined, { withoutEnlargement: true });
if (pngs.includes(ext)) {
initial = initial.png(pngOptions);
} else if (jpgs.includes(ext)) {
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 805da1d43..ee32de152 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -100,7 +100,7 @@ function buildWithMiddleware(server: express.Express) {
passport.session(),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
res.locals.user = req.user;
- if (req.originalUrl.endsWith('.png') /*|| req.originalUrl.endsWith(".js")*/ && req.method === 'GET' && (res as any)._contentLength) {
+ if ((req.originalUrl.endsWith('.png') || req.originalUrl.endsWith('.jpg') || (process.env.RELEASE === 'true' && req.originalUrl.endsWith('.js'))) && req.method === 'GET') {
const period = 30000;
res.set('Cache-control', `public, max-age=${period}`);
} else {
@@ -149,23 +149,15 @@ function registerAuthenticationRoutes(server: express.Express) {
function registerCorsProxy(server: express.Express) {
server.use('/corsProxy', async (req, res) => {
- const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : '';
- let requrlraw = decodeURIComponent(req.url.substring(1));
- const qsplit = requrlraw.split('?q=');
- const newqsplit = requrlraw.split('&q=');
+ //const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : '';
+ let requrl = decodeURIComponent(req.url.substring(1));
+ const qsplit = requrl.split('?q=');
+ const newqsplit = requrl.split('&q=');
if (qsplit.length > 1 && newqsplit.length > 1) {
const lastq = newqsplit[newqsplit.length - 1];
- requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1];
- }
- const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw;
- // cors weirdness here...
- // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative,
- // then we redirect again to the cors referer and just add the relative path.
- if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) {
- res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl);
- } else {
- proxyServe(req, requrl, res);
+ requrl = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1];
}
+ proxyServe(req, requrl, res);
});
}
@@ -184,7 +176,9 @@ function proxyServe(req: any, requrl: string, response: any) {
const htmlText = htmlInputText
.toString('utf8')
.replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- .replace(/href="https?([^"]*)"/g, httpsToCors)
+ // .replace('<script', '<noscript')
+ // .replace('</script', '</noscript')
+ // .replace(/href="https?([^"]*)"/g, httpsToCors)
.replace(/data-srcset="[^"]*"/g, '')
.replace(/srcset="[^"]*"/g, '')
.replace(/target="_blank"/g, '');
@@ -234,8 +228,10 @@ function proxyServe(req: any, requrl: string, response: any) {
function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
server.use('*', (req, res) => {
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')) {
+ // if (req.originalUrl === '/css/main.css' || req.originalUrl === '/favicon.ico') res.end();
+ // else
+ if (!res.headersSent && req.headers.referer?.includes('corsProxy')) {
+ 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
// a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here.
try {
const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)